Lisp's Influence on Ruby
Posted by tacoda 5 days ago
Comments
Comment by jksmith 2 days ago
Comment by Blikkentrekker 2 days ago
I also like how in Haskell:
something =
{ element
, element1
, element2
, element3
}
Is an actually idiomatic way to deal with the lack of trailing commata.Comment by kazinator 2 days ago
MyClass::MyClass(foo bar, int arg1, int arg2)
: Base(bar)
, member1(arg1)
, member2(arg2)
{
}Comment by shawn_w 2 days ago
Comment by NuclearPM 1 day ago
Comment by Ferret7446 2 days ago
Comment by chirsz 2 days ago
Comment by atcol 2 days ago
Comment by iLemming 2 days ago
Comment by andsoitis 2 days ago
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.)
Comment by jasaldivara 2 days ago
Comment by tacoda 2 days ago
Comment by arunix 2 days ago
Comment by chabska 2 days ago
Comment by veqq 2 days ago
Literally the opposite. We can make and use so many, because writing them is more or less the same. We can quickly throw together a new lisp for a new platform or such and use it without problem.
Comment by allthetime 2 days ago
Comment by tacoda 2 days ago
Comment by arikrahman 2 days ago
Comment by slifin 2 days ago
I do love that I learnt Clojure once like 5-7 years ago and more and more dialects keep expanding the choice of runtime I can target
Comment by arikrahman 14 hours ago
Comment by jksmith 2 days ago
Comment by hyperrail 2 days ago
orders
.select { |o| o.placed_at > 1.week.ago }
.group_by(&:customer_id)
.transform_values { |group| group.sum(&:total) }
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.)
Comment by tmtvl 2 days ago
(~> orders
(filter (lambda (order)
(timestamp> (order-date order)
(timestamp- (now) 7 :days))))
(group-by #'order-customer-id)
(mapcar (lambda (group)
(reduce #'+ group :key #'order-total)))
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.Comment by evdubs 2 days ago
Comment by whartung 2 days ago
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):
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).
Comment by harryposner 2 days ago
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: (sequence (comp (map model/find-thing)
(filter some?))
things)
It looks like there's a Common Lisp transducers library, but I have no idea how widely it's used.Comment by kagevf 2 days ago
edit SICP has examples on how to implement streaming (in Scheme).
Comment by evdubs 2 days ago
Comment by matheusmoreira 2 days ago
Comment by Blikkentrekker 2 days ago
<exp> |> <exp2>
<exp2>(<exp>)
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.
Comment by emidln 2 days ago
[0] https://github.com/dtenny/clj-arrows
Comment by sph 2 days ago
[1,2,3]
|> Enum.map(&square/1)
|> Enum.filter(&odd?/1)
Using a threading operator where there is no such consistency is painful. This is why I dislike CL’s or Python’s map function, taking the list to operate on as second argument, instead of first. A threading operator wouldn’t be as effective there.Comment by shawn_w 2 days ago
Comment by Blikkentrekker 1 day ago
Comment by 0x3444ac53 2 days ago
It was one of the first programming languages I was introduced to at 16 or so, but an older person that I looked up to told me it would get me stuck in "hobby coder land". He was wrong in so many ways, but even if he was right, I wanna have fun in my hobby code :)
Comment by Kaliboy 2 days ago
I applied cause the listing mentioned Python, and I was programming in Python at the time.
Once I started they were like yeah we put that there to reach a broader public but we use Ruby (on Rails).
So that's what I learned. I've just returned to Python via LLM's. I literally have not felt the need nor desire to use Python once I got used to writing Ruby.
Comment by dismalaf 2 days ago
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...
Comment by vidarh 2 days ago
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.
Comment by Syzygies 2 days ago
I'm Ruby or Lean 4.
Comment by rjsw 2 days ago
Comment by dismalaf 2 days ago
Also I'm working on a DSL/Macros that give me more Ruby-esque quality of life things in Lisp.
Comment by ralphc 2 days ago
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.
Comment by tmtvl 2 days ago
Comment by vindarel 1 day ago
Comment by evw 2 days ago
Comment by pluralmonad 2 days ago
Comment by neilv 2 days ago
Reminds me of an email I wish I still had.
Circa 2000, I wrote that I was leaning towards moving to Scheme, for more rapid R&D work than I could do in Java.
Some nice-sounding person I didn't know emailed me from Japan, to mention a language I hadn't heard of, called Ruby.
I don't know whether the person was Yukihiro Matsumoto himself, but it's a small world.
Comment by muvlon 2 days ago
Where Ruby's lisp lineage really shows is the fact that it's got Kernel#callcc, aka call with current continuation. It doesn't get any lispier than that!
Comment by t0mpr1c3 1 day ago
Comment by insumanth 2 days ago
Comment by DonHopkins 2 days ago
Comment by BoingBoomTschak 2 days ago
Comment by wild_egg 2 days ago
Comment by jonjacky 2 days ago
Comment by pjmlp 2 days ago
Comment by 0xpgm 2 days ago
> 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.
Comment by wglb 2 days ago
Comment by dismalaf 2 days ago
But yeah, macros and S-expressions make it easier to write your own DSLs.
Comment by pjmlp 2 days ago
For better or worse, parenthesis aren't that bad with the proper IDE tooling.
Comment by to11mtm 2 days ago
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.
Comment by draegtun 21 hours ago
Even Ruby's trailing blocks syntax are an homage to Perl's block list subroutines:
# first Ruby example in article
users.select { |u| u.admin? }.map(&:email)
# using Perl's block list
map {$_->email} grep {$_->is_admin} @users;Comment by Smalltalker-80 2 days ago
Comment by riffraff 2 days ago
(Matz speaking at the LL2 conference some 20+ years ago)
Comment by dragonwriter 2 days ago
Comment by p_l 2 days ago
Comment by danlitt 2 days ago
Put the macros back! It would be so cool!
Comment by KerrAvon 2 days ago
Comment by yxhuvud 2 days ago
Comment by bashkiddie 2 days ago
well, except for pattern matching. That is just syntax.
Comment by matheusmoreira 2 days ago
Comment by ameliaquining 2 days ago
Comment by matheusmoreira 2 days ago
Comment by ameliaquining 2 days ago
Comment by matheusmoreira 2 days ago
The lispy "macros" I speak of are FEXPRs, just everyday normal functions that just happen to not evaluate their arguments, they receive the source code as lists instead. It's easy to manipulate those lists and evaluate the result.
Lisps themselves moved away from FEXPRs because they were "too powerful" and made the compiler's life hard. Common Lisp and Scheme macros are the more restricted versions that allow compilers to make more assumptions, thereby enabling more aggressive optimization.
Comment by steveklabnik 2 days ago
The latter is basically a function from token streams to token streams, and macros by example are more traditional macros which were initially designed by Dave Herman, who was heavily involved in Racket.
Comment by ameliaquining 2 days ago
I don't see why this would inhibit optimization, unless you mean it slows down compilation, in which case, yep, that's a real and rather notorious downside.
Comment by matheusmoreira 2 days ago
That's actually amazing. So the compiler's own data structures are visible in the language.
I see how it works now. Thanks for explaining.
Comment by shawn_w 2 days ago
Comment by somewhereoutth 2 days ago
Comment by tug2024 2 days ago