tag:blogger.com,1999:blog-12983281.post-1149214168218670632006-06-01T18:59:00.000-07:002006-06-01T19:10:19.250-07:00Stealing it backAt <a href="http://www.artima.com">Artima</a>, Topher Cyll wrote an article called <a href="http://www.artima.com/rubycs/articles/patterns_sexp_dslsP.html">If It's Not Nailed Down, Steal It</a> where he implements pattern matching, S-expressions and destructuring in Ruby, then uses this to implement a modified Logo interpreter (it uses "(" and ")" instead of "[" and "]"). The Logo interpreter outputs SVG files for rendering. <br /><br />The interpreter interested me, and hey, Lisp already has the stuff he's stealing, so I thought I'd have a go at implementing the interpreter. I wanted to stick to "vanilla Common Lisp" as much as possible, in other words not pull in big pattern matching libraries, just stick to CLOS multiple dispatch. <br /><br />Most of the "stealing back" is pretty straightforward, except for the "run" methods. It looks like the Ruby code can distinguish methods by variable arguments, where Lisp methods implementing a generic function must all have the same argument signature, even if the last argument is a &rest. Fortunately, the Ruby methods just distinguish by the command argument, and the rest is destructuring, so once we're in the dispatched run method we can destructure afterwards.<br /><br />The next bump was chopping off the "command" argument from the remainder of the S-expression to call the next run, which left a single argument for the &rest, the argument being a list of the remaining arguments, so that had to be car'ed, and you had to remember to do it everywhere, etc. It's messy. <br /><br />"apply" to the rescue. From the CLHS, "apply" uses a "spreadable argument list designator", which means it will turn (1 2 (3 4 5)) into (1 2 3 4 5) when it gets back to dispatching on the method. So<br /><pre><code>(apply #'run lg '(right 90 forward 10))</code></pre><br />turns into<br /><pre><code>(run lg right 90 forward 10)</code></pre><br />which will match on<br /><pre><code>(defmethod run ((lg logo) (command (eql 'right)) &rest args)</code></pre><br />and "args" will have (90 forward 10) in it, ready for destructuring.<br /><br />One feature of the Ruby version that I can't figure out how to duplicate is dispatching on nil, in other words when we're at the end of the program S-expression. It looks like the methods can catch it, but calling "run" or "apply #'run" won't throw it. What I came up with was inserting a special "stop" token at the end of the programs and stopping on it, or checking at the end of each method for an empty remainder before calling run again. I opted for the empty check. If there's a better solution I'd appreciate a comment to let me know.<br /><br />This gives me methods such as<br /><pre><code><br />(defmethod run ((lg logo) (command (eql 'forward)) &rest args)<br /> (destructuring-bind (distance &rest rest-args) args<br /> (move lg distance)<br /> (when rest-args (apply #'run lg rest-args))))<br /><br />(defmethod run ((lg logo) (command (eql 'pendown)) &rest args)<br /> (setf (pen lg) t)<br /> (when args (apply #'run lg args)))<br /><br />(defmethod run ((lg logo) (command (eql 'repeat)) &rest args)<br /> (destructuring-bind (repeat-count code &rest rest-args) args<br /> (dotimes (i repeat-count) (apply #'run lg code))<br /> (when rest-args (apply #'run lg rest-args))))<br /></code></pre><br /><br />This works, but looking at the code you can see a lot of boilerplate code. Like any Lisper, to me this code smelled like it needed a macro to make an internal DSL to help out with the external DSL. So with<br /><pre><code><br />(defmacro defrun ((logo-var command-symbol vars) &body body)<br /> (let ((args (gensym))<br /> (rest-args (gensym)))<br /> `(defmethod run ((,logo-var logo) (command (eql ',command-symbol)) &rest ,args)<br /> (destructuring-bind (,@vars &rest ,rest-args) ,args<br /> ,@body<br /> (when ,rest-args (apply #'run ,logo-var ,rest-args))))))<br /></code></pre><br /><br />the above methods turn into<br /><pre><code><br />(defrun (lg forward (distance)) (move lg distance))<br />(defrun (lg pendown ()) (setf (pen lg) t))<br />(defrun (lg repeat (repeat-count code)) (dotimes (i repeat-count) (apply #'run lg code)))<br /></code></pre><br />which is closer to the Ruby DSL.<br /><br /><a href="http://blubparadox.googlepages.com/logo-steal-when.lisp">logo-steal-when.lisp</a> has the pre-macro version, and <a href="http://blubparadox.googlepages.com/logo-steal-macro.lisp">logo-steal-macro.lisp</a> has the defrun'ned version. Happy stealing.<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/12983281-114921416821867063?l=i-need-closures.blogspot.com'/></div>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com12