Can Ruby pop like Lisp?

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?

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.

You could force a deep copy:

y = Marshal.load(Marshal.dump(x.first))

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?

It's getting modified because both are just references to the original.
(shallow copy)

irb(main):001:0> x = [[:a, :b], :c]
=> [[:a, :b], :c]
irb(main):002:0> y = Marshal.load(Marshal.dump(x.first))
=> [:a, :b]
irb(main):003:0> y.shift
=> :a
irb(main):004:0> y
=> [:b]
irb(main):005:0> x
=> [[:a, :b], :c]

···

On Wednesday 05 October 2005 18:41, waterbowl@gmail.com wrote:

Hi --

···

On Thu, 6 Oct 2005 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?

If you want to alter an array that's like x[0], but isn't x[0], you
can use dup:

   y = x[0].dup
   y.shift

David

--
David A. Black
dblack@wobblini.net

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

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

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?

Erm, if you just want the first element, you can use indexed access
ar[-1]. And if you want to append to a copy you can use ar + ["foo"]. If
you prefer methods, you can simply do

def push(a,x)
  a +
end

def pop(a) a[-1] end

Kind regards

    robert

waterbowl@gmail.com said:

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:

Here's one way. As someone else pointed out, there is a fundamental
difference between Ruby's arrays and Lisp's lists. But given that, here
is a way to mimic the 'macro nature' of lisp's pop function (i.e. changing
the value of "y" from withing pop):

···

------------------------------------------------------

require 'test/unit'

# (setq x '((a b) c)) ; => ((A B) C)
# (setq y (car x)) ; => (A B)
# (pop y) ; => A
# y ; => (B)
# x ; => ((A B) C)

class TestPop < Test::Unit::TestCase
  def test_pop
    x = [[:a, :b], :c]
    y = x.first

    assert_equal :a, pop {:y}

    assert_equal x, [[:a, :b], :c]
    assert_equal y, [:b]
  end
end

def pop(&block)
  var = block.call.to_s
  value = eval var, block
  setter = eval "proc { |value| #{var} = value }", block
  setter.call(value[1..-1])
  value.first
end

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

Hi --

···

On Thu, 6 Oct 2005, Kevin Brown wrote:

On Wednesday 05 October 2005 18:41, 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.

You could force a deep copy:

y = Marshal.load(Marshal.dump(x.first))

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?

It's getting modified because both are just references to the original.
(shallow copy)

Actually there's no copying at all in the original example -- it's
just a reference to the same object. dup will give you a shallow copy
(i.e., a different array, but containing the same objects).

David

--
David A. Black
dblack@wobblini.net

Thx for the replies!

David A. Black wrote:

···

Hi --

On Thu, 6 Oct 2005, Kevin Brown wrote:

> On Wednesday 05 October 2005 18:41, 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.
>
> You could force a deep copy:
>
> y = Marshal.load(Marshal.dump(x.first))
>
>> 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?
>
> It's getting modified because both are just references to the original.
> (shallow copy)

Actually there's no copying at all in the original example -- it's
just a reference to the same object. dup will give you a shallow copy
(i.e., a different array, but containing the same objects).

David

--
David A. Black
dblack@wobblini.net

A different array that contains the same objects is a deep copy. A reference
to the same array is called a shallow copy, because the two 'pointers' point
to the same physical memory. (jee, can you tell I came to Ruby from C++?)

···

On Wednesday 05 October 2005 20:51, waterbowl@gmail.com wrote:

Thx for the replies!
David A. Black wrote:
> Actually there's no copying at all in the original example -- it's
> just a reference to the same object. dup will give you a shallow copy
> (i.e., a different array, but containing the same objects).

I would disagree. The terminology I'm used to using and hearing states it as David did:

A shallow copy is a new object of the same type holding references to the same objects as the original.

A deep copy is a new object of the same type, where each 'child' object in the original is (recursively) deep copied.

···

On Oct 5, 2005, at 9:07 PM, Kevin Brown wrote:

A different array that contains the same objects is a deep copy. A reference
to the same array is called a shallow copy, because the two 'pointers' point
to the same physical memory. (jee, can you tell I came to Ruby from C++?)

Hi --

Thx for the replies!
David A. Black wrote:

Actually there's no copying at all in the original example -- it's
just a reference to the same object. dup will give you a shallow copy
(i.e., a different array, but containing the same objects).

A different array that contains the same objects is a deep copy. A reference
to the same array is called a shallow copy, because the two 'pointers' point
to the same physical memory. (jee, can you tell I came to Ruby from C++?)

I highly recommend coming from Ruby when you use Ruby. It makes
everything much more consistent, and ultimately more enjoyable :slight_smile:

In Ruby you'll find that, given this:

   x = [1,2,3]
   y = x

what you've got is a copy of the reference, but not a copy of the
array.

I've never heard the term "shallow copy" applied to a copy of a
reference. It's actually an *exact* copy of the reference, and not a
copy in any sense of the array object itself.

As for dup: the ri description of Object#dup says, in part:

      Produces a shallow copy of obj---the instance variables of obj
      are copied, but not the objects they reference. dup copies the
      tainted state of obj.

As you can see, the "shallow copy" produced by dup is, indeed, a
different array, but containing the same objects:

   x = ["hi"]
   y = x.dup
   x[0].equal?(y[0]) # true

That's completely standard usage in Ruby. "Deep copy", on the other
hand, means recursing through a container object and copying the
contents as well as the container itself:

   x = ["hi"]
   y = Marshal.load(Marshal.dump(x))
   x[0].equal?(y[0]) # false

David

···

On Thu, 6 Oct 2005, Kevin Brown wrote:

On Wednesday 05 October 2005 20:51, waterbowl@gmail.com wrote:

--
David A. Black
dblack@wobblini.net

Which is what I just said minus the recursively. I apologize for missing that
crucial piece. What was originally stated by David was that a different
array containing the same objects was a shallow copy. It is not fully deep,
nor is it fully shallow. That's all.

···

On Wednesday 05 October 2005 21:57, Gavin Kistner wrote:

On Oct 5, 2005, at 9:07 PM, Kevin Brown wrote:
> A different array that contains the same objects is a deep copy. A
> reference
> to the same array is called a shallow copy, because the two
> 'pointers' point
> to the same physical memory. (jee, can you tell I came to Ruby from
> C++?)

I would disagree. The terminology I'm used to using and hearing
states it as David did:

A shallow copy is a new object of the same type holding references to
the same objects as the original.

A deep copy is a new object of the same type, where each 'child'
object in the original is (recursively) deep copied.

I think you and I are still saying different things. Here's what I (and I believe David) am saying.

a,b = 'a', 'b'
c = [ a, b ]
same = c
shallow = c.dup
deep = [ a.dup, b.dup ]

'same' is a reference to the same array. It is not, in any way, a copy. This seems to be what you called a 'shallow copy'.

'shallow' is what I would call a shallow copy - you can push a new element onto the array without affecting the original, but if you mutate the existing elements in the array, you affect the elements in the original. (For example, shallow[0]<<'HEY' will affect c[0].) This seems to be what you called a 'deep' copy.

'deep' is what I would call a deep copy. (Although normally I would use a more automated technique to create it :slight_smile:

···

On Oct 5, 2005, at 10:17 PM, Kevin Brown wrote:

On Wednesday 05 October 2005 21:57, Gavin Kistner wrote:

On Oct 5, 2005, at 9:07 PM, Kevin Brown wrote:

A different array that contains the same objects is a deep copy. A
reference
to the same array is called a shallow copy, because the two
'pointers' point
to the same physical memory. (jee, can you tell I came to Ruby from
C++?)

I would disagree. The terminology I'm used to using and hearing
states it as David did:

A shallow copy is a new object of the same type holding references to
the same objects as the original.

A deep copy is a new object of the same type, where each 'child'
object in the original is (recursively) deep copied.

Which is what I just said minus the recursively. I apologize for missing that
crucial piece. What was originally stated by David was that a different
array containing the same objects was a shallow copy. It is not fully deep,
nor is it fully shallow. That's all.

> Which is what I just said minus the recursively. I apologize for
> missing that
> crucial piece. What was originally stated by David was that a
> different
> array containing the same objects was a shallow copy. It is not
> fully deep,
> nor is it fully shallow. That's all.

I think you and I are still saying different things. Here's what I
(and I believe David) am saying.

a,b = 'a', 'b'
c = [ a, b ]
same = c
shallow = c.dup
deep = [ a.dup, b.dup ]

'same' is a reference to the same array. It is not, in any way, a
copy. This seems to be what you called a 'shallow copy'.

I understand it's not a copy. In C++ terminology that I learned it's called a
shallow copy due to the fact that it contains a 'copy' of the data but two
deletes will crash becase there are only really two references. The default
copy constructor will do this for you if you don't overload it when you do
dynamic memory allocation. Hence shallow copy. I didn't decide this
terminology, and it may disagree with what many say on the subject. That's
fine, it's the terminology I learned, and that's all.

'shallow' is what I would call a shallow copy - you can push a new
element onto the array without affecting the original,

The terminology I learned states that any shallow copy did not duplicate any
of the elements, just another reference (pointer) pointing to the original.
I'm not trying to argue, because I really don't care, just trying to clarify
what I was saying before. I understand that no actual copying takes place
whatsoever, except that in C++ you copy the value of the pointer to the new
one.

but if you
mutate the existing elements in the array, you affect the elements in
the original. (For example, shallow[0]<<'HEY' will affect c[0].)
This seems to be what you called a 'deep' copy.

Because usually in C++ you only manage one level down. Any object1 = object2
that you do will be managed by the objects themselves with their own
assignment operator or copy constructor, negating the need to traverse down
and do a 'full' deep copy.

'deep' is what I would call a deep copy. (Although normally I would
use a more automated technique to create it :slight_smile:

I would call that deep as well, I'm just not used to having deep to a certain
level copies as it's hard to achieve in C++. You either get a reference or a
full copy tree.

···

On Thursday 06 October 2005 08:01, Gavin Kistner wrote:

On Oct 5, 2005, at 10:17 PM, Kevin Brown wrote:

I didn't decide this terminology, and it may disagree with what many say on the subject. That's fine, it's the terminology I learned, and that's all.

No worries. I'm not arguing either; I was simply helping to clarify that those terms mean different things in the Ruby world.

I would call that deep as well, I'm just not used to having deep to a certain
level copies as it's hard to achieve in C++. You either get a reference or a
full copy tree.

I suppose it's not trivial in Ruby either, which is why we have the Marshal dump/load 'hack'.

···

On Oct 6, 2005, at 8:17 AM, Kevin Brown wrote:

Er, wait. Now *I'm* misunderstanding. Ignore what I said, as it does not relate to what you said :slight_smile:

···

On Oct 6, 2005, at 8:43 AM, Gavin Kistner wrote:

On Oct 6, 2005, at 8:17 AM, Kevin Brown wrote:

I would call that deep as well, I'm just not used to having deep to a certain
level copies as it's hard to achieve in C++. You either get a reference or a
full copy tree.

I suppose it's not trivial in Ruby either, which is why we have the Marshal dump/load 'hack'.