Lispy - Code-as-data in Ruby, without the metaprogramming madness.
github.com
github.com
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.
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.
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.
: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.
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.
AR is actually an okay example - it's as good an illustration as any of a place that employs these kinds of techniques, and the source of a lot of complexity in Rails.
Anyway, I've been thinking about this kind of stuff, and it seemed like a good opportunity to mention it.
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.
If you want an example of something cool you can do with lisp, in principal the common lisp object system is built with macros on top of the non-object oriented lisp. Could i use Lispy to add a CLOS style object system on top of ruby? What about a prototype based one? This isn't a rhetorical question, I'm actually curious if ruby can do these kinds of things.
What features of CLOS are you interested in? I just whipped up some CLOS-style parameter-matching method invocation in Ruby: https://gist.github.com/926156
I'm not sure how you'd do that with Lispy. But it's definitely possible in Ruby.
If you Google "prototype-based object model in Ruby" you get at least one solution: http://www.google.com/search?hl=en&q=prototype-based+obj...
http://www.gigamonkeys.com/book/practical-a-simple-database....
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.
Also, if a fullblown lisp is what you want, then I wonder whether the correct alternative isn't 'embed a lisp interpreter'. If Lispy is not intended to be as powerful as a fullblown Lisp, then perhaps it just suffices?
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.
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.
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.
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.
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.
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.
I found the README distracting and hard to follow. I'm still unclear just what the library does or why one might need it, possibly because of an over-emphasis on jokes over technical detail and sufficient examples.
However, there seems to be an under-current of "Ruby is hard to use correctly, so use this."
Do people really find Ruby meta-programming so hard?
C++ metaprogramming hell -- ohhhh, that's real.
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/
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.I blame seeing too much of clojure's "defn fart [ args ]" syntax ;-)
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.)
http://www.artima.com/rubycs/articles/patterns_sexp_dslsP.ht...
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 .)
Man, you're such a rebel!!
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.