Now that I'm out of the corporate tyranny and have my own company, I use lisp for everything. There's certain satisfaction in writing config files and persisting data directly in s-expressions. Any json requirements are triggered by exports to foreign systems.
Which Lisp, out of interest?
Does it really matter? There's a point in every Lisper's life, a threshold after which the question becomes immaterial - you'd stop thinking about intricacies of whatever Lisp and focus on the platform specifics instead. Any given day I might program in three-four different Lisp dialects, e.g. Clojure/Clourescript, Fennel, Elisp, Janet, etc. and it practically feels like I'm using the same PL. While switching between TS and JS (same family) never feels even close - there's always some mental burden.
> Does it really matter?
not philosophically, but certainly practically. To double down, if all lisps are roughly equivalent from a language POV, then you'd want to pick the one or two that will give you the most practical advantage (libraries, documentation, dev environment, etc.)
Even the Lisps have Lisps. Like Clojure with ClojureScript, CLR, ClojureDart, Jank... etc.
That JSON prohibits trailing commata makes it an absolute pain to work with in practice.
Is an actually idiomatic way to deal with the lack of trailing commata.
I did something like that in C++ circa 1998, before seeing it anywhere else:
MyClass::MyClass(foo bar, int arg1, int arg2)
: Base(bar)
, member1(arg1)
, member2(arg2)
{
}
You see that style in SQL too.
Not really? A linter/formatter takes care of it.
One way I find traditional Lisp style more painful for functional code than Ruby is that fully functional-style Lisp pushes me to read and write code the opposite way from how I think about it. In the author's example:
the equivalent Lisp code would either be written in imperative style as multiple statements that each write to a temporary variable or (let) binding, or would look like this:
(reduce #'+
(map (lambda (o) (getf o 'total))
; this group_by replacement function
; might be written as hash-table code
(my-group-by 'customer-id
(remove-if-not
(lambda (o)
(>
(getf o 'placed-at)
(- (my-now) (* 60 60 24 7))))
orders))))
where I now have to read from bottom to top to understand the order of operations on the `orders` record set, even though when I wrote the code earlier, I "logically" thought from first operation to last when deciding which high-level operations to use in which order.
Other imperative languages that support functional code either make you do things imperatively to get the "logical" ordering of functional operations like I feel Lisp pushes you to do, or they do something like Ruby where things can be chained left to right in a "single" statement even for operations that were not thought of ahead of time by the creators of opaque data structures you later need to operate on. (Everything is a user-extensible object like Ruby, unified function call syntax in D, extension methods in C#, or pipelines of structured objects in PowerShell.)
But I prefer the typical Lisp code where I get the sums of the totals of the orders with the same customer ID which were placed in the past week, instead of the orders made the past week grouped by customer ID their totals summed together.
In contrast to, say, Java (I can't speak to the code above):
List<Things> things = thingIds.stream()
.map(model::findThing)
.filter(Objects::nonNull)
.toList();
These are streamed. This is pretty much a pipe structure, whereas the threading macros will create a lot of temporary copies of the data (I don't know if that's a universal truth). That is, if you're processing a 1000 items, say `gather` returns a 1000 items, that 1000 item list is passed to `uppercase-list` which return a new 1000 item list to feed to `sort` which returns another 1000 item list (assuming none of these are destructive).
I wish CL had something like the Java streams (maybe it does).
Clojure has two options:
The version with a threading macro, will create a lazy-sequence for each step in the pipeline. It will not instantiate the entire list, so it's O(1) memory overhead in terms of peak memory, but it churns O(N) extra garbage.
(->> things
(map model/find-thing)
(filter some?))
And the version with transducers, which will not create any intermediate sequences:
I feel languages should just have some kind of sugar or operator for this, in fact in Ocaml the |> operator exists where
<exp> |> <exp2>
<exp2>(<exp>)
Are just one and the same
For a variadic language you'd need something more involved though. But some kind of syntax can probably be invented in some language.
It's common to write the thrush combinator as a lisp macro. Clojure ships ->, ->>, as->, some->, some->>, cond->, and cond->> out of the box. You can find similar macros for CL[0], Racket[1], and a scheme SRFI[2]. Writing them is a fun exercise in your lisp of choice if you don't have a library available.
For folks that want all of this plus macros (and a lot of other great things), check out Elixir.
100% Elixir is much more a Lisp than Ruby is.
Agree that Elixir is closer to a Lisp than Ruby.
Heck at least in my brain MLs are closer to a Lisp than Ruby...
> He’s described Ruby’s design as starting from a simple Lisp, stripping out macros and s-expressions
Put the macros back! It would be so cool!
Macros depend on homoiconicity which Ruby sacrificed in order to have familiar syntax.
Homoiconicity makes macros slightly more syntactically elegant, but is not at all necessary. Rust has macros and isn't homoiconic at all.
You kind of don't need them in Ruby, because everything is a method or an object or a closure and you can dynamically create and alter those at runtime. That's why Ruby is really good for ad-hoc DSLs in ways that Rust and Swift really are not.
Crystal don't have the dynamicity but has macros to get the next best thing. Most meta magic in Ruby in good code are done at startup anyhow so you don't miss out on that much. YMMV.
> because everything is a method or an object or a closure
well, except for pattern matching. That is just syntax.
Put the s-expressions back too.
[deleted]
That is actually Lisp influence on Smalltalk, and Perl, that eventually influenced Ruby.
From the article
> Matz has said as much. He’s described Ruby’s design as starting from a simple Lisp, stripping out macros and s-expressions, then adding an object system, blocks, and Smalltalk-style methods. The features most Rubyists fall in love with aren’t the object-oriented ones. They’re the functional ones, dressed in friendlier clothes.
But macros and s-expressions are two of my favorites parts of lisp!
Funny enough Lisp was originally meant to be written in a higher level syntax (with infix operators and everything).
But yeah, macros and S-expressions make it easier to write your own DSLs.
With decades later, Dylan and Julia becoming the only ones that kind of managed to get some adoption doing it.
For better or worse, parenthesis aren't that bad with the proper IDE tooling.
> For better or worse, parenthesis aren't that bad with the proper IDE tooling.
Hell, even without [0], you can at least count the parenthesis by hand in a pinch. I remember seeing lots of crazy-awesome stuff done in AutoLisp by 'non-programmers', versus 'structure as spacing' in Python which really sucks if the Editor was designed to use the system default (probably non-monospaced, cause other products in the industry had dialogs that broke if you switched to a monospaced) font. [1]
[0] - but real talk parenthesis matching in an editor is a lifesaver
[1] - oooooold version of a very popular GIS product.
Matz directly credits Lisp (through Emacs Lisp) as influence in the design of Ruby and its runtime, with Smalltalk influence on the language itself, and IIRC Perl as "what was popular and we tried to replace"
Totalle agree, I just googled it:
"Yukihiro 'Matz' Matsumoto heavily credits Smalltalk as the deepest structural inspiration behind Ruby’s object model. He combined Smalltalk’s beautiful object-oriented architecture and message-passing system with features from other languages to create a tool designed primarily for developer happiness."
Including the closures and collection operations.
"Some may say Ruby is a bad rip-off of Lisp or Smalltalk, and I admit that. But it is nicer to ordinary people."
(Matz speaking at the LL2 conference some 20+ years ago)
No, its actual influence from Lisp-family languages (including Scheme). Yes, Lisp also influenced Perl and Smalltalk, but Matz was not ignorant of Lisp with the only influence om Ruby from Lisp being indirect through those other languages.
I love Ruby, use it for most of my projects that don't require performance.
Nothing I would love more than a Ruby with a Common-Lisp like compiler and runtime. Unboxed types, native compilation, partial compilation, live image (Ruby has this but "faster Rubies" like Crystal don't), etc...
I have a (self-hosted, but buggy and wildly incomplete; don't try to use - jRuby or TruffleRuby are better - and far faster - options) Ruby compiler that was partly born out of wanting to figure out what this would take, and the answer is it is massively painful because Ruby has failed to take some basic steps that makes delineating read-time and run-time very hard (e.g. you have fun patterns like overriding "require", and iterating over directories to decide what to require) even though most Ruby programs do have clearly separate load and run phases. It's just hard to programmatically separate it.
I still believe you could do pretty well there with a few basic "tricks" that could still also remain real/valid Ruby, by recognising the most common patterns, documenting them, and providing a way of marking exceptions. Combine that with freezing system classes after startup as an enabler for various optimization, and a compiler could do a pretty good job. But it's a massive piece of work to get it right for Ruby.
I came close to adopting Scala, many parallels to Ruby with vastly better performance.
I'm Ruby or Lean 4.
... or just use Common Lisp.
Which is what I do. One can dream though right? Of a world where Ruby stayed just a tad more Lisp-y and less Perl/C/Smalltalk/Unix-y.
Also I'm working on a DSL/Macros that give me more Ruby-esque quality of life things in Lisp.
Common Lisp, and even more so Racket, has reader macros. With a little help from LLMs you might be able to get a Ruby-like language that translates into Lisp.
As a last resort look at Racket's "Rhombus" language, it's basically an infix, Python-like syntax on top of Racket. You can use that or see how they pull it off and add Ruby constructs to it.
was this before or after Lisp's epiphany for lexical binding?
Now that I'm out of the corporate tyranny and have my own company, I use lisp for everything. There's certain satisfaction in writing config files and persisting data directly in s-expressions. Any json requirements are triggered by exports to foreign systems.
Which Lisp, out of interest?
Does it really matter? There's a point in every Lisper's life, a threshold after which the question becomes immaterial - you'd stop thinking about intricacies of whatever Lisp and focus on the platform specifics instead. Any given day I might program in three-four different Lisp dialects, e.g. Clojure/Clourescript, Fennel, Elisp, Janet, etc. and it practically feels like I'm using the same PL. While switching between TS and JS (same family) never feels even close - there's always some mental burden.
> Does it really matter?
not philosophically, but certainly practically. To double down, if all lisps are roughly equivalent from a language POV, then you'd want to pick the one or two that will give you the most practical advantage (libraries, documentation, dev environment, etc.)
Even the Lisps have Lisps. Like Clojure with ClojureScript, CLR, ClojureDart, Jank... etc.
That JSON prohibits trailing commata makes it an absolute pain to work with in practice.
I also like how in Haskell:
Is an actually idiomatic way to deal with the lack of trailing commata.I did something like that in C++ circa 1998, before seeing it anywhere else:
You see that style in SQL too.
Not really? A linter/formatter takes care of it.
One way I find traditional Lisp style more painful for functional code than Ruby is that fully functional-style Lisp pushes me to read and write code the opposite way from how I think about it. In the author's example:
the equivalent Lisp code would either be written in imperative style as multiple statements that each write to a temporary variable or (let) binding, or would look like this: where I now have to read from bottom to top to understand the order of operations on the `orders` record set, even though when I wrote the code earlier, I "logically" thought from first operation to last when deciding which high-level operations to use in which order.Other imperative languages that support functional code either make you do things imperatively to get the "logical" ordering of functional operations like I feel Lisp pushes you to do, or they do something like Ruby where things can be chained left to right in a "single" statement even for operations that were not thought of ahead of time by the creators of opaque data structures you later need to operate on. (Everything is a user-extensible object like Ruby, unified function call syntax in D, extension methods in C#, or pipelines of structured objects in PowerShell.)
It could just be written like:
But I prefer the typical Lisp code where I get the sums of the totals of the orders with the same customer ID which were placed in the past week, instead of the orders made the past week grouped by customer ID their totals summed together.Threading macros are nice, though, right?
https://docs.racket-lang.org/threading/introduction.html
They're nice, but they're not the same thing.
The threading macros are (as I understand it) pure sugar.
Turning (-> (gather my-list) uppercase-list sort) into (sort (uppercase-list (gather my-list))).
In contrast to, say, Java (I can't speak to the code above):
These are streamed. This is pretty much a pipe structure, whereas the threading macros will create a lot of temporary copies of the data (I don't know if that's a universal truth). That is, if you're processing a 1000 items, say `gather` returns a 1000 items, that 1000 item list is passed to `uppercase-list` which return a new 1000 item list to feed to `sort` which returns another 1000 item list (assuming none of these are destructive).I wish CL had something like the Java streams (maybe it does).
Clojure has two options:
The version with a threading macro, will create a lazy-sequence for each step in the pipeline. It will not instantiate the entire list, so it's O(1) memory overhead in terms of peak memory, but it churns O(N) extra garbage.
And the version with transducers, which will not create any intermediate sequences: It looks like there's a Common Lisp transducers library, but I have no idea how widely it's used.https://github.com/fosskers/transducers
Apparently, the Series library offers that. It didn't make it into the ANSI standard, but it's still maintained and covered in CLtL2.
edit SICP has examples on how to implement streaming (in Scheme).
I am pretty sure Racket's `stream` will handle this use case.
https://docs.racket-lang.org/reference/streams.html
Love those.
I feel languages should just have some kind of sugar or operator for this, in fact in Ocaml the |> operator exists where
Are just one and the sameFor a variadic language you'd need something more involved though. But some kind of syntax can probably be invented in some language.
It's common to write the thrush combinator as a lisp macro. Clojure ships ->, ->>, as->, some->, some->>, cond->, and cond->> out of the box. You can find similar macros for CL[0], Racket[1], and a scheme SRFI[2]. Writing them is a fun exercise in your lisp of choice if you don't have a library available.
[0] https://github.com/dtenny/clj-arrows
[1] https://docs.racket-lang.org/threading/index.html
[2] https://srfi.schemers.org/srfi-197/srfi-197.html
For folks that want all of this plus macros (and a lot of other great things), check out Elixir.
100% Elixir is much more a Lisp than Ruby is.
Agree that Elixir is closer to a Lisp than Ruby.
Heck at least in my brain MLs are closer to a Lisp than Ruby...
> He’s described Ruby’s design as starting from a simple Lisp, stripping out macros and s-expressions
Put the macros back! It would be so cool!
Macros depend on homoiconicity which Ruby sacrificed in order to have familiar syntax.
Homoiconicity makes macros slightly more syntactically elegant, but is not at all necessary. Rust has macros and isn't homoiconic at all.
You kind of don't need them in Ruby, because everything is a method or an object or a closure and you can dynamically create and alter those at runtime. That's why Ruby is really good for ad-hoc DSLs in ways that Rust and Swift really are not.
Crystal don't have the dynamicity but has macros to get the next best thing. Most meta magic in Ruby in good code are done at startup anyhow so you don't miss out on that much. YMMV.
> because everything is a method or an object or a closure
well, except for pattern matching. That is just syntax.
Put the s-expressions back too.
That is actually Lisp influence on Smalltalk, and Perl, that eventually influenced Ruby.
From the article
> Matz has said as much. He’s described Ruby’s design as starting from a simple Lisp, stripping out macros and s-expressions, then adding an object system, blocks, and Smalltalk-style methods. The features most Rubyists fall in love with aren’t the object-oriented ones. They’re the functional ones, dressed in friendlier clothes.
But macros and s-expressions are two of my favorites parts of lisp!
Funny enough Lisp was originally meant to be written in a higher level syntax (with infix operators and everything).
But yeah, macros and S-expressions make it easier to write your own DSLs.
With decades later, Dylan and Julia becoming the only ones that kind of managed to get some adoption doing it.
For better or worse, parenthesis aren't that bad with the proper IDE tooling.
> For better or worse, parenthesis aren't that bad with the proper IDE tooling.
Hell, even without [0], you can at least count the parenthesis by hand in a pinch. I remember seeing lots of crazy-awesome stuff done in AutoLisp by 'non-programmers', versus 'structure as spacing' in Python which really sucks if the Editor was designed to use the system default (probably non-monospaced, cause other products in the industry had dialogs that broke if you switched to a monospaced) font. [1]
[0] - but real talk parenthesis matching in an editor is a lifesaver
[1] - oooooold version of a very popular GIS product.
Matz directly credits Lisp (through Emacs Lisp) as influence in the design of Ruby and its runtime, with Smalltalk influence on the language itself, and IIRC Perl as "what was popular and we tried to replace"
Totalle agree, I just googled it: "Yukihiro 'Matz' Matsumoto heavily credits Smalltalk as the deepest structural inspiration behind Ruby’s object model. He combined Smalltalk’s beautiful object-oriented architecture and message-passing system with features from other languages to create a tool designed primarily for developer happiness." Including the closures and collection operations.
"Some may say Ruby is a bad rip-off of Lisp or Smalltalk, and I admit that. But it is nicer to ordinary people."
(Matz speaking at the LL2 conference some 20+ years ago)
No, its actual influence from Lisp-family languages (including Scheme). Yes, Lisp also influenced Perl and Smalltalk, but Matz was not ignorant of Lisp with the only influence om Ruby from Lisp being indirect through those other languages.
I love Ruby, use it for most of my projects that don't require performance.
Nothing I would love more than a Ruby with a Common-Lisp like compiler and runtime. Unboxed types, native compilation, partial compilation, live image (Ruby has this but "faster Rubies" like Crystal don't), etc...
I have a (self-hosted, but buggy and wildly incomplete; don't try to use - jRuby or TruffleRuby are better - and far faster - options) Ruby compiler that was partly born out of wanting to figure out what this would take, and the answer is it is massively painful because Ruby has failed to take some basic steps that makes delineating read-time and run-time very hard (e.g. you have fun patterns like overriding "require", and iterating over directories to decide what to require) even though most Ruby programs do have clearly separate load and run phases. It's just hard to programmatically separate it.
I still believe you could do pretty well there with a few basic "tricks" that could still also remain real/valid Ruby, by recognising the most common patterns, documenting them, and providing a way of marking exceptions. Combine that with freezing system classes after startup as an enabler for various optimization, and a compiler could do a pretty good job. But it's a massive piece of work to get it right for Ruby.
I came close to adopting Scala, many parallels to Ruby with vastly better performance.
I'm Ruby or Lean 4.
... or just use Common Lisp.
Which is what I do. One can dream though right? Of a world where Ruby stayed just a tad more Lisp-y and less Perl/C/Smalltalk/Unix-y.
Also I'm working on a DSL/Macros that give me more Ruby-esque quality of life things in Lisp.
Have you checked out dieggsy's Whisper (<https://sr.ht/~dieggsy/whisper/>) yet? It's based on Arne Bab's Wisp (SRFI 119).
Common Lisp, and even more so Racket, has reader macros. With a little help from LLMs you might be able to get a Ruby-like language that translates into Lisp.
As a last resort look at Racket's "Rhombus" language, it's basically an infix, Python-like syntax on top of Racket. You can use that or see how they pull it off and add Ruby constructs to it.
was this before or after Lisp's epiphany for lexical binding?
What have the Lisps ever done for us?
https://www.youtube.com/watch?v=Qc7HmhrgTuQ
Always fun to remind grugs that LISP invented "if" and GC.
[dead]