Hi,
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
···
On Oct 5, 2005, at 8:41 PM, waterbowl@gmail.com wrote:
----
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
}