Can Ruby pop like Lisp?

W

waterbowl

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?
 
K

Kevin Brown

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]
 
D

David A. Black

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?

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
 
D

David A. Black

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.

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
 
W

waterbowl

Thx for the replies!
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.

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
 
K

Kevin Brown

Thx for the replies!

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++?)
 
G

Gavin Kistner

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.
 
K

Kevin Brown

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.
 
B

Bob Hutchison

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

----
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)
}
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
}
 
R

Robert Klemme

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 + [x]
end

def pop(a) a[-1] end

Kind regards

robert
 
D

David A. Black

Hi --

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 :)

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
 
G

Gavin Kistner

--Apple-Mail-1--23791011
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

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'.

'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 :)

--Apple-Mail-1--23791011--
 
K

Kevin Brown

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 :)

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.
 
G

Gavin Kistner

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'.
 
G

Gavin Kistner

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

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

Jim Weirich

(e-mail address removed) 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. changin=
g
the value of "y" from withing pop):

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

require 'test/unit'

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

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

assert_equal :a, pop {:y}

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

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


--=20
-- Jim Weirich (e-mail address removed) http://onestepback.org
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top