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 <http://www.recursive.ca/hutch/>
Recursive Design Inc.  -- <http://www.recursive.ca/>
Raconteur              -- <http://www.raconteur.info/>


#!/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
}