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 ?
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.
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.