

Lispy - Code-as-data in Ruby, without the metaprogramming madness. - chrislloyd
https://github.com/ryan-allen/lispy

======
raganwald
I like it, and I speak as someone who is slowly recovering from a year in a
sanatorium gibbering about how I killed Mozart by using a library to decompile
Ruby into sexps, then rearranged the sexps before reconstructing Ruby code:

<https://github.com/raganwald/rewrite_rails>

I'd still be in a padded cell if I hadn't run into a doctor who eschewed the
more complex forms of therapy and simply told me to stop doing that.

------
bguthrie
I really, really like this idea. It's difficult enough to employ Ruby's
metaprogramming constructs without making a mess, so the idea of instantly
decomposing into nested lists is really appealing.

I realize the library is only, like, three days old, but questions/feedback:

\- Do the DSL methods get turned into into proper Ruby methods eventually? I'd
hate to see this whole thing built on the back of method_missing.

\- Picking apart nested arrays can lead to its own kind of spaghetti code.
When people build DSLs, I recommend they immediately turn their high-level
method calls into nicely testable objects. Can we see an example of that here?

\- The author specifically calls out libraries like ActiveRecord, which would
suggest that the primary use case for this library is for integration as a DSL
layer into your own libraries, but he doesn't provide any examples of that
usage--just Lispy.new. I'd like a way to extend Lispy into my own class or
module and get that same functionality.

~~~
ryan-allen
G'day,

Thanks for the feedback! I was hoping to do a little more work on it before it
being posted to somewhere like HN, but oh well!

A lot of the work I've done with this kind of Ruby, it often only is run once,
so I tend to use method_missing. The reason why you'd want it converted to
methods is for performance, right?

I agree with your testing comments, there's a lot left out so far,
specifically a guide on how you might use such a library, I'm working on that.

And yes, you're right about AR being not the best example, it's use is for
what you've described. Integration with your own code would be an ideal use.
Implementing it as a module would be trivial.

~~~
pi18n
Is there a specific reason you chose to do arrays of arrays instead of hashes
for subelements? I'd want the lists to come out as (maybe using a pluralizer
or maybe not)

:items => [{:priority => :normal, :desc => '...'}, ...]

Then you could do more intuitive things like

todo_list.items.sort{|a,b| a[:priority] <=> b[:priority] }

which is somewhat stupid for symbols but not for numbers.

~~~
ryan-allen
No, no reason why I chose arrays, it's just a first cut. I was thinking of
converting the representation for objects as the meaning of the three possible
elements isn't explicit at the moment.

~~~
alttab
One thing to keep in mind is different Ruby implementations, versions, and
platforms run differently. An array is currently the safest way to maintain
order when it matters.

If order didn't matter, than a hash would be fine, but iterating over the data
could cause unwanted behavior if order is important and hash keys are served
to an iterator in a different order than was laid out by the DSL code.

------
pavelludiq
OT: I'm very tempted to be an asshole smug lisp weenie, and make some
Greenspun's rule joke, but instead I'm going to try to be helpful. Ruby
hackers pride themselves for being early adopters of new and cool technology,
learning new skills, and libraries. What I've failed to notice is any interest
in the old technologies, from which ruby got most of its good ideas(lisp,
smalltalk). There is tremendous value in learning from the past(a lot of
really good ideas didn't make it to the "cool kids languages" of today). This
isn't specific to ruby, but its a good example. So my attempt at useful advice
is: do learn lisp, instead of reinventing the wheel badly.

Anyway, I've been thinking about this kind of stuff, and it seemed like a good
opportunity to mention it.

~~~
erikpukinskis
_learn lisp, instead of reinventing the wheel badly_

When someone says something like this, my typical response is "That's true, I
should dig more into Lisp. Oh, look! A bug in my software I need to fix. *
forgets Lisp entirely *"

If you were to say something like "the difference between Lispy and Lisp is
that in Lisp you can do X", then my likelihood of actually going out and
learning about X, pulling out the Lisp interpreter and playing around goes up
about 1000x.

~~~
spacemanaki
I was so impressed by pavelludiq's comment (I had the impulse to post a
comment citing Greenspun but refrained, their comment was so much better than
what I would have posted) and I tried to come up with a tiny example of a
defmacro which would show where Lispy is reinventing the wheel, but really,
why bother when much better writers and teachers have already. I think Peter
Seibel's Practical Common Lisp is a great intro and this chapter is so early
in the book it nearly stands on its own:

[http://www.gigamonkeys.com/book/practical-a-simple-
database....](http://www.gigamonkeys.com/book/practical-a-simple-
database.html)

There's a couple of simple macros in there and it's not that long a read.

Then there's Casting SPELs in Lisp which is another great concise intro to
macros that shows them off:

<http://www.lisperati.com/casting.html>

I don't think I could do better than that. Both of those are short enough that
you can read them in an evening. If you're not hooked by then, at least you
haven't spent much time on it.

~~~
erikpukinskis
Thanks!

------
KirinDave
I'm a bit confused what this has to do with lisp. Seems more like a classic
Smalltalk building pattern. If it's a reference to "code as data" from the
Lisp tradition (which is a friendlier colloquialism for "homoiconicity"), then
they bear only a tenuous relationship.

Which is not to say that this library wouldn't be useful. But it is to say
that it doesn't really bring any of the power of homoiconicity or
consequential techniques thereof to Ruby.

------
sleight42
The man has a point. We should consider using sexps more often when developing
internal DSLs.

That's not to say (stupid iPad) that we dont still need define_method and
instance_eval; however, decoupling the language definition/API from the
language impl is good sauce.

------
JonnieCache
_Ruby metaprogramming hell (it is a real place)._

But meta-sin is so much fun!

Nice lib with a lot of potential. It will definitely come in handy and may
well prompt me to take some unfinished projects off the back burner and get
them in the oven as it were.

Also, I enjoy your README writing style. It walks the fine line between
amusing irreverence and technical specificity very well.

~~~
bitwize
Ruby metaprogramming hell is a myth used to frighten school children into
doing the dishes.

C++ metaprogramming hell -- ohhhh, that's _real_.

------
zwp
From the Examples:

    
    
        fart => [ :fart, [] ]
        fart 1 => [ :fart, 1 ]
        fart 1, 2 => [ :fart, [ 1, 2 ] ]
    

Why isn't the single argument call:

    
    
        fart 1 => [ :fart, [ 1 ] ]
    

?

ps. this is also fun: <http://parsetree.rubyforge.org/ruby_parser/>

~~~
dustmop
That's funny, I was expecting the opposite behavior:

    
    
        fart => [ :fart ]
        fart 1 => [ :fart, 1 ]
        fart 1, 2 => [ :fart, 1, 2 ]
    

Seems more lispy to me, and from a quick glance I can't see where it would
break.

~~~
zwp
Yes, I prefer this, definitely more lispy.

I blame seeing too much of clojure's "defn fart [ args ]" syntax ;-)

------
extension
I called this a Domain Agnostic Language:

<http://code.extension.ws/post/169602795/dal-rb>

I don't actually like the approach though. Ruby makes DSLs as easy as "normal"
interfaces. Why not take advantage of that? Generating and parsing an AST is
exactly the kind of extra complexity that Ruby lets you avoid.

~~~
raganwald
Here's a use case: Imagine that you want a form validation library, something
like active record's domain-specific language, but it is not attached to
models, just to forms being submitted. But anyhow, you write your DSL. Now you
have two options:

1\. Execute it directly in the controller of your Ruby server 2\. Convert it
to sexps, export those as JSON, and write a Javascript validator that
validates forms on the client-side.

~~~
extension
That is indeed a nifty use case, but also kind of a special case in which a
serialized AST is the required output of your program. For the general case
where the DSL is just a sexy API, I wouldn't recommend building a parse tree.

EDIT: Actually, for your case I don't think I would use sexps. I would write
some primitive validators in Ruby that can also generate equivalent JavaScript
code, then use those to build higher order validators in Ruby alone. Compare
that to maintaining complete validator engines in two different languages.

------
samps
This looks pretty cool, but as a non-Rubyist, I remain confused about the
purpose of all these "DSL" libraries that lispy aims to simplify.

In the examples, at least, the language was only used to declaratively build
hierarchical list/dictionary structures. The only constructs I can see used
are blocks and method invocations; there aren't any loops or conditionals or
anything else imperative. Can someone more experienced with Ruby DSLs explain
what lispy accomplishes that something like YAML or a simple custom parser
doesn't? (Other than a bunch of extra "do"s and ":"s.)

~~~
erikpukinskis
There aren't any loops or conditionals in the example, but since it's using
blocks, there could be arbitrary code. But yeah, if you're not doing any of
that you might as well just use YAML, or a hash.

------
jamesbritt
How does this compare to the 'sexp' library?

[http://www.artima.com/rubycs/articles/patterns_sexp_dslsP.ht...](http://www.artima.com/rubycs/articles/patterns_sexp_dslsP.html)

------
soamv
I see that lispy is using instance_exec -- which means that the scope of code
inside that code is the lispy instance, which might not be what the programmer
expects. For example if he does this inside a method he'd find his instance
variables missing. (Is that right, or am I misreading the code?)

You could use some sort of global or thread local variable to maintain the
context, and then you wouldn't lose the instance scope, but then this code
will not mix well with yield (and continuations and fibers etc.)

So then it looks like the only way to solve this problem really neatly is in a
language that has macros.

(I made some attempts at this problem too, at <https://github.com/soam/blox> .
It is slightly more complex than lispy; see for example
<https://github.com/soam/blox/tree/master/samples/webservice> .)

------
mardigan
There was a recent ruby-talk announcement for live parse trees being available
in 1.9 now. Presumably gives the functionality of ParseTree.
<http://quix.github.com/live_ast>

------
helium
This is very close to something I built earlier this year...just nicer.

<https://github.com/michael-erasmus/Flippant>

------
kleiba
_Saying that, I've written about 10 lines of LISP in my life. Pete keeps
telling me to go read SICP and I keep meaning to but then I end up at the
local bar listening to some rock band and I'm like "crap, it's 3am"._

Man, you're such a rebel!!

------
moron4hire
"Code-as-data in Ruby, without the metaprogramming madness. — Read more
<http://www.youtube.com/watch?v=aD4bn5pp32w>

link is a rickroll. What was the point of that? I don't think we need to be
super serious about everything, but at least don't be anti-productive. I was
hoping to have a link to a talk or screen-cast demonstrating what Lispy was
about. Instead, backwards rickroll.

~~~
ryan-allen
I'm sorry! I've updated the link to the RubyGem. I'm also working on a
tutorial for intended usage, I'll post it when it's done.

------
jcapote
1000 internets to this man. This is excellent!

