I wonder how much faster that would have pushed the world into FP ideas. While sometimes I prefer the bracket/C syntax, I wonder how things would have evolved if JS was a lisp originally. Instead of things moving to TypeScript, would they be moving to something like typed Lisp or OCaml, or PureScript ?
https://news.ycombinator.com/item?id=33676959 - November 2022, 64 comments
https://news.ycombinator.com/item?id=9363635 - April 2015, 45 comments
Devs in the 90s were handed a language that looked like a weird Java and so they programmed it like a weird Java. If they were handed a language that looked like Lisp instead, maybe they would have made their way to SICP that much sooner.
Sure, but CL also culturally has a long history of macro-ridden code that make it harder to understand other people's code.
We already suffer from dozens of JS frameworks. Just imagine what it would look like under a CL-dominant world...
Idk man, every time someone makes that claim my immediate reaction is: "what's the catch?". I much rather use 5 tools designed for specific purposes than general-purpose tools that are 50% good at 5 tasks.
> you can literally pause on an exception, rewind, fix your code and continue from where you left off.
Does it only work on source codes or can I distribute a binary and let my users debug the code like this? Should I distribute the 'image' for it to work?
And is the fix temporary (until the program dies) or permanant?
The restart system is complex, harder to implement, and harder to reason about (it has more "spooky action at a distance", much of it determined at runtime and not compile time).
The "wisdom of the crowds" will generally favor systems that are either:
1. Easier to implement (including sustainment work in the future)
2. Easier to use (or reason about in this case)
Sometimes you get things that are both, but it's often not possible (or feasible) to find or make systems that are both easy to use and easy to implement.
Checked exceptions and explicit error returns are one of those things that happen to provide both (1) and (2) (sort of on 2: often more upfront work, but better reasonability). They're easier to implement for language developers (everything is known at compile time, there is less needed in terms of runtime support especially for explicit error returns), and they're easier to create static tooling for which helps users (programmers). Even without tooling, the explicitness and locality of information makes them more reasonable.
Unchecked exceptions are harder to implement (for instance, the need for handling cleanup as you unwind the stack, which could happen at any point in a function's execution), and they are harder to build static tooling for and harder for programmers to reason about. Common Lisp's conditions and restarts are even harder on all fronts.
This isn't a bad thing, it's a powerful tool. But it means that it won't be mainstream once easier alternatives come along. All the wisdom of the crowds has told us on this is that checked exceptions and explicit error returns are easier. Not that one is better or worse than the other.
As I alluded to earlier, its really hard to scale a dev team when the language does nothing to keep you on the rails. As an engineer, I hate go for its lack of abstractions and verbosity. As a CTO, I can appreciate that its trying to reduce the friction in making sure all code looks familiar and that any engineer can be rotated into it. TLDR: the things that make common lisp so good for a lone dev are what make it hard for larger projects and most projects nowadays have multiple contributors. I wouldn't start a startup on common lisp today unless you were trying to do something truly novel and your team was all seasoned and experienced devs. throwing a bunch of vibe coding juniors on common lisp is a recipe for disaster while you might make it to a series A using a language like go.
Personally, I love elixir as I think it strikes a really good balance. My team is all older programmers. Our youngest guy is 32 and we have all developed a pretty good intuition for maintaining a descent code base.
> Does it only work on source codes or can I distribute a binary and let my users debug the code like this? Should I distribute the 'image' for it to work?
I wouldn't hand it to the end user but paul grahm famously did cowboy debugging on live servers. A user would cal complaining of a error and paul could go in and patch it in real time while observing the runtime of the system the user was on.
I think it goes without saying that that was a different time and we def can't do that kind of thing today.
> And is the fix temporary (until the program dies) or permanant?
you patch teh code and reload it into your running vm. so its permanent.
I think, Lisp in general is very flexible - you can write imperatively, or you can do more FP; if you need object-orientation - you have it, polymorphic dispatch - sure why not?, etc., etc.
Link to book on Amazon: https://www.amazon.com/dp/0367350203
(defun double-numbers (numbers)
"Doubles a list of NUMBERS.
Docstring and type declarations are optional."
(declare (type List numbers))
(mapcar (lambda (x)
(declare (type Number x))
(* x 2))
numbers))
(defun double-numbers (numbers)
(labels ((accumulate-loop (nums acc)
(if (null nums)
(funcall acc nums)
(destructuring-bind (first-number &rest other-numbers)
nums
(accumulate-loop other-numbers
(lambda (result)
(funcall acc
(cons (* first-number 2)
result))))))))
(accumulate-loop numbers (lambda (result) result))))
(defun double-numbers (numbers)
(if (null numbers)
numbers
(cons (* (first numbers) 2)
(double-numbers (rest numbers))))
(defun double-numbers (numbers)
(loop :for number :in numbers
:collect (* number 2)))
(defun double-numbers (numbers)
(if (null numbers)
numbers
(prog ((remaining-numbers (rest numbers))
(first-pair (list (* (first numbers) 2)))
last-pair)
(setf last-pair first-pair)
:start-loop
(when (null remaining-numbers)
(return first-pair))
(setf (rest last-pair)
(list (* (pop remaining-numbers) 2)))
(setf last-pair (rest last-pair))
(go :start-loop))))
And that's before going into libraries like SERIES or iterate. There are, of course, benefits and disadvantages (readability, performance, some CL implementations don't do TCO,...) to every option, but generally CL lets you code in any way you want (you could even write some macros to let you write it using more infix or postfix style syntax, but I don't see the appeal of doing so). for(int i = 0; i < N; ++i) { ... }
becomes foldl (fn (i, acc) => ...) 0 (range N)
It's technically recursion, but I don't really see any of it, and I don't really think about it that way.But do give yourself this gift - even if you end up preferring imperative style for day-to-day work, learning Haskell or Clojure can be genuinely eye-opening.
It's like learning a foreign language - even if you never become fluent, it changes how you think about your native language. You'll start seeing patterns and abstractions you missed before, even in imperative code.
upd: sorry, only after posting it I noticed your "as someone who's done both". Just wanted to point out that here my suggestion is aimed not to you directly, but to a "proverbial" programmer, I used "you" in more general sense.
I can't say how many times I've reinvented pieces of Common Lisp to do my job. Now I want to start a side project with this.
... but it doesn't have to be that way. Proper tree-shaking of libraries and smart caching of common resources should make it possible for that cost to get minimized or amortized.