From: Bob Hutchison Date: 2005-10-06T14:17:13+09:00 Subject: Re: Can Ruby pop like Lisp? Hi, On Oct 5, 2005, at 8:41 PM, waterbowl@gmail.com wrote: > Is it possible to write a method in Ruby that acts like pop does in > Lisp? Array#shift is an obvious candidate but there's a difference. > For > example: > > Ruby: > > irb(main):001:0> x = [[:a, :b], :c] > => [[:a, :b], :c] > irb(main):002:0> y = x.first > => [:a, :b] > irb(main):003:0> y.shift > => :a > irb(main):004:0> y > => [:b] > irb(main):005:0> x > => [[:b], :c] > irb(main):006:0> > > Lisp: > > [2]> (setq x '((a b) c)) > ((A B) C) > [3]> (setq y (car x)) > (A B) > [4]> (pop y) > A > [5]> y > (B) > [6]> x > ((A B) C) > > y.shift causes x to be modified whereas Lisp's (pop y) does not modify > x. > > If Ruby had macros we could use them to define pop. Given that it does > not, is there some other way to define a method to do this? > These are very different data structures. The arrays in ruby are like vectors in lisp, push and pop work on the end of the array/vector. What you need is a list data structure in Ruby, and you don't need macros for that -- macros will only fix up the syntax really. On top of that lisp has something that I think are called 'places', and setq operates on those things. Places give you an extra level of indirection -- this is why pop and push work. If Ruby's ObjectSpace provided something that allowed you to set what ObjectSpace._id2ref returns, then you would have the equivalent. There's some code at the end of this post that does something like what you want. Probably lots of bugs, I threw this together pretty quickly. It does have a cute lispy syntax (it is just Ruby syntax with peculiar parenthesisation -- I kind of thought ruby was a lot like lisp :-) Kind of fun, but I don't know how useful. Maybe. Cheers, Bob > > ---- Bob Hutchison -- blogs at Recursive Design Inc. -- Raconteur -- #!/usr/bin/env ruby require 'stringio' class Place attr_accessor :ref def initialize(thing) @ref = thing # make sure we don't have nested Places while @ref.kind_of?(Place) do @ref = @ref.ref; end end def method_missing(symbol, *args) if @ref then if 0 < args.size then result, new_ref = @ref.send(symbol, args) else result, new_ref = @ref.send(symbol) end if new_ref then # when new_ref is defined the the method is tring to modify its 'self' # (e.g. push and pop, but not list, car, cons) @ref = new_ref # make sure we don't have nested Places while @ref.kind_of?(Place) do @ref = @ref.ref; end end return result end return nil end def to_s @ref ? @ref.to_s : "" end def null nil == @ref end end class Cons attr_accessor :_first, :_rest def initialize(first=nil, rest=nil) @_first = setq(first) @_rest = setq(rest) end def car return @_first end def cdr return @_rest end def cons(first) return Cons.new(first, self) end def Cons.list(*args) things = args.first list = setq(Cons.new) (things.size - 1).step(0, -1){ | i | list = list.cons(things[i]) } return list end def push(thing) new_cons = Cons.new(thing, self) return new_cons, new_cons end def pop return @_first, @_rest end def to_s_unwrapped if @_first and @_rest and @_rest._first and !@_rest._first.null then "#{@_first} #{@_rest.to_s_unwrapped}" elsif @_first then "#{@_first}" else "" end end def to_s return "(#{to_s_unwrapped})" end end def car(cons) cons.car end def push(thing, list) return list.push(thing) end def pop(cons) return cons.pop end def setq(thing) Place.new(thing) end def list(*things) Cons.list(things) end #this binding provides a place to 'remember' local definitions in the REP @rep_binding = binding def rep(script) # Read-Eval-Print (but no Loop, so REP not REPL) input = StringIO.new(script) input.each{ | command | puts "\t>> #{command}" puts eval(command, @rep_binding) } end rep %Q{ x = (setq (list (list :a, :b), :c)) y = (setq (car x)) z = (pop y) y x z l = (list) (push :a, l) (push :b, l) (push :c, l) (push :d, l) (pop l) l (pop l) l (pop l) l (pop l) l } rep %Q{ x y z l }