read


read
or learn more

Lisp in 32 lines of Ruby

Jan 25, 2012

Playing around yesterday during lunch.1

[sourcecode lang=”ruby” gist=”1677501″]You are being redirected.[/sourcecode]

And the LISP ecosystem has been enriched — much like manure to the garden.

SO I PUT A LANGUAGE IN YUR LANGUAGE SO YOU CAN HACK WHILE YOU HACK!

uLithp Github repo — fork and reduce!

:F

this post was discussed on various posts and podcasts including: Ruby5, HackerNews, and Ruby Inside. BTW, this is now 2927 25 lines.


  1. I also made some mods a few minutes after posting to reduce the count to 32 lines. Inspired by http://www.brool.com/index.php/the-tiniest-lisp-in-python 

35 Comments, Comment or Ping

  1. Cool, now you can use it in ‘any sufficiently complicated Ruby program’

  2. Beautiful!

    The only part I struggle to understand is the last line in apply:

        self.eval @env[fn][2], Hash[*(@env[fn][1].zip args).flatten]
    

    Is this possibly the start of you adding user defined functions? Or am I totally missing something obvious?

  3. @Sam

    Yes, that handles the user-defined functions case. That line actually has a bug, it should use flatten(1). I’ve updated the code with an example definition of :second. Basically that line, builds a function-specific environment.

  4. Jaskirat

    Nice! Reminded me of Peter Norvig’s post on the same. http://norvig.com/lispy.html

  5. @fogus Ah, makes complete sense – especially now there’s an example.

    Again, beautiful work :-)

  6. @Jaskirat

    I’ve read that post a few times. Love it! His Lisp has the advantage of providing a fuller experience with a REPL. It’s more inline with my Lithp impl. http://fogus.me/fun/lithp

  7. Yeah, I noticed that this one doesn’t have a repl like experience, but hey it’s 32 lines and pretty darn succinct at that! :)

    Excellent! lithp seems to be sprinkled with more than generous documentation. Let me check it out right away.

  8. This should be called “risp” and pronounced the way Matz would pronounce it.

  9. Where does :lambda get introduced? Is it just being unwrapped as an unrecognized symbol and using Ruby’s own lambda?

  10. @McCormack

    It’s a trick. That is, the env can hold atoms, lambda (i.e. callable things), and “something” else. This “something else” is assumed to be a Lithpy lambda of the form [:lambda, [<args>], <body>]. See my example defining :second to see how to use it. The cool part is you do not need to use :lambda… you could use any name you want and still achieve lambda-ness.

  11. Carin Meier

    Very nice :)

  12. Sean Nilan

    Very cool!

    One thing I noticed is that the above doesn’t allow for closures though. Changing the following line should work:

    self.eval @env[fn][2], Hash[*(@env[fn][1].zip args).flatten(1)] to self.eval @env[fn][2], ctx.merge(Hash[*(@env[fn][1].zip args).flatten(1)])

  13. Mike

    I’m suddenly feeling very dim because I’ve never seen the |(a,b), _| syntax. I presume that the underscore is something like “Any number of arguments”?

    My google-fu has turned up nothing helpful. Anyone have a link to an up-to-date online reference for Ruby 1.9 that covers this?

  14. @Mike, _ is a convention used when you don’t really care about that variable. So that block expects 2 variables: a pair (a,b) and another variable that the author doesn’t care about, _. You see it more often in functional languages. I used it here (http://j.mp/zrj5IA) when implementing Float#round in Rubinius for 1.9 mode. I didn’t care about the first return value of Math.frexp(self), only the second.

  15. @Mike

    My use of _ is just to indicate that I don’t need to use that variable in the body of the block. There is nothing special about that name as far as I know.

  16. The inevitable conclusion of this is to write it in 140 characters :D

  17. Mike

    @Jesse Cooke & @fogus

    WHEW! I’m glad that’s “just” an idiom, though I still don’t think I’m going to recover my Ruby Confidence (TM) for at least a week.

  18. Congrats for such a compact implementation! I reproduced it in javascript and earned 1 line! :D

    gist -> https://gist.github.com/1679611

  19. Very interesting. How would you implement a function like ‘const’? E.g., “lambda { |x| lambda { |y| x }}”?

  20. Petrik de Heus

    Managed to get it down to 21 lines with some cheating. https://gist.github.com/1679766

  21. Petrik de Heus

    Oops, deleted it by accident: https://gist.github.com/1680015

  22. What th… Oh! This is madness, dude. But it is also unspeakably cool, so it’s OK ;)

  23. This is exactly why I love both lisp and Ruby. Terrific native data structures like Ruby arrays & hashes and a beautifully simple language design like lisp s-expressions summarized in a short code example. I’m teary-eyed!

  24. An S-Expression (i.e. LISP) parser in 32 lines of Ruby:

    https://gist.github.com/1684926

    :-)

    Russ

  25. And to finish the thought, a REPL that uses my s-expression parser and Fogus’ code above:

    https://gist.github.com/1684947

    R

    PS in 12 lines of code.

  26. Wowwww! I’m in love with LISP, it’s the best programming language ever, and now I’m playing with Ruby a bit… It’s amazing how much Ruby is powerful…

    Maybe you can enhance your code to accept something like cadddr, cddaaar, etc… (they’re all a mix of car and cdr in any case).

  27. Petrik de Heus

    Managed to simplify it to 17 lines and added specs :) https://gist.github.com/1680015

  28. theldoria

    I also stumbled across the |(x,y),_| syntax, not for the _, but for the braces. I can not find any documentation about them, however, they seem to change the behaviour in some repect. For example, compare the following returned values: irb(main):105:0> ->(&b){b.parameters}.() {|(a)|} => [[:opt, nil]] irb(main):106:0> ->(&b){b.parameters}.() {|a|} => [[:opt, :a]]

    I also found a usage for the braces with Hash iterator: irb(main):109:0> {key: :value}.map {|(a)| a} => [:key] irb(main):110:0> {key: :value}.map {|a| a} => [[:key, :value]]

    Is that behaviour desired? Documented anywhere? Or is it experimental?

  29. theldoria

    Ah, now I know what it was: the implicit splat with a nested level:

    irb(main):049:0> [[:a, :b]].map {|x| x} => [[:a, :b]] irb(main):050:0> [[:a, :b]].map {|(x)| x} => [:a]

  30. Joel Gluth

    Comments #1 and #8 both made me burn myself laughing while trying to drink soup. Great post, and actually (now that I think of it) a pretty decent advertisement for Ruby.

  31. I like the way you wrote this tiny parser, it’s really cool, so I wrote the a tiny parser of brainf**ck, based on your LISP code (see https://github.com/parttimenerd/tiny-bf).

  32. Seems like you can kill another line with this: return ctx[sexpr] || sexpr

  33. tamouse

    Oh, Hats off to You!! This is absolutely awesome! Very well done. :))

  34. Sam Umbach

    @fogus: What’s the motivation for ignoring the first element of the array [:lambda, [], ]?

  35. @sam

    I just couldn’t figure a way to enforce that symbol without adding a line. Suggestions welcomed.

Reply to “Lisp in 32 lines of Ruby”