Project

General

Profile

Bug #1302 » callcctest_demo_map.rb

Sarwen (Christophe Calves), 03/20/2009 11:44 PM

 
require 'continuation'

# The idea of the code is to run '[0].map &f'
# with '&f' stopping the execution of 'map'
# (by aborting the computation (calling k0))
# and storing the continuation in 'x' (to
# resume it later). Calling 'x' with a value
# 'y' should resume the continuation with the
# value 'y' which will send it the the map
# ('[0].map { |x| y } and thus returning [y].
# This is the idea of making a Zipper.


def runtest
x = callcc { |k0| r0 = [0].map { |x| callcc { |k1| print "\nCaptured!\n"
k0.call k1
} }
@savecont.call r0
}
# x = k1
# = the continuation pointing to where
# callcc { |k1| .... } was called


puts "\n=> x"
print " x = ", x.to_s, "\n"

# We call 'x' two times with values 1 and 2
# The resulting values should be respectively
# [1] and [2]


print "\nBegin of x1\n============\n"

x1 = callcc { |k2| @savecont = k2
x.call 1
}


print "\n x1 = ", x1.to_s
puts ""

# x1 = { r = [0].map { |x| 1 }
# @savecont.call r
# }
# = k2 [1]
# = [1]


print "\n\nBegin of x2\n============\n"

# Now doing the same with '2' instead of '1'
# We should get [2]

x2 = callcc { |k2| @savecont = k2
x.call 2
}

print "\n x2 = ", x2.to_s, "\n"
print " x1 = ", x1.to_s, "\n"
end



# The code is run with 5 versions of map
# - The first one is the actual map implemented
# in ruby (see array.c in the sources)
# - The second one is a version having the same
# effects as the actual map
# - The thrid one is a sligly modified version of
# the third one computing the tail last
# - The fourth one is a version using only persisent
# arrays with tail computed first
# - The last one is another version using only persistent
# arrays but with tail computed last




print "\n\
################\n\
# THE RUBY MAP #\n\
################\n\n"


# The code of Array.map can be found in array.c
# (line 1895 of the current version) :
#
# /*
# * call-seq:
# * array.collect {|item| block } -> an_array
# * array.map {|item| block } -> an_array
# *
# * Invokes <i>block</i> once for each element of <i>self</i>. Creates a
# * new array containing the values returned by the block.
# * See also <code>Enumerable#collect</code>.
# *
# * a = [ "a", "b", "c", "d" ]
# * a.collect {|x| x + "!" } #=> ["a!", "b!", "c!", "d!"]
# * a #=> ["a", "b", "c", "d"]
# */
#
#
# static VALUE
# rb_ary_collect(VALUE ary)
# {
# long i;
# VALUE collect;
#
# RETURN_ENUMERATOR(ary, 0, 0);
# collect = rb_ary_new2(RARRAY_LEN(ary));
# for (i = 0; i < RARRAY_LEN(ary); i++) {
# rb_ary_push(collect, rb_yield(RARRAY_PTR(ary)[i]));
# }
# return collect;
# }


runtest


# When computing x1 :
# Indeed when '1' is sent to 'x', the 'map'
# receive the value '1' and finishes. All is
# as expected. 'x1' the array [1]
# 'x' is still the same procedure.
#
# When computing x2 :
# we get [1,2] instead of [2] and 'x1' changed.




print "\n\n\
###################################\n\
# NON PERSISTANT MAP : TAIL FIRST #\n\
###################################\n\n"

# This map has the same effect as the actual one
# when computing arr.map &f
# we compute q = arr[0..-2].map &f
# then t = f.call arr[-1]
# and then push t in q (q << t)
# this operation does changes q

class Array
def map &f
if self.empty?
[]
else
print "\nComputing q ... "
q = self[0..-2].map &f
print "q = ", q.to_s

print "\nCalling f ... "
t = f.call self[-1]
print "\nOut of f ... q = ", q.to_s

q << t
print "\nPush ... q = ", q.to_s
q
end
end
end


runtest

# When computing x1 :
# Indeed when '1' is sent to 'x', the 'map'
# receive the value '1' and finishes. All is
# as expected. 'x1' the array [1]
#
# When computing x2 :
# 'x2' should be [2] but is [1,2]
# Because we pushed t into q when computing x1,
# when we call x again, q is not empty any more
# but has its previous value ([1]). Then we push
# 2 into q, modifing it again.
# x1 being pointing to q, it is modified too.


print "\n\
###################################\n\
# NON PERSISTANT MAP : HEAD FIRST #\n\
###################################\n\n"


# This map have the correct behavior IN THIS EXAMPLE
# It is a sligly modified version of the previous one
# where instead of compyting q = arr[0..-2].map &f
# before t = f.call arr[-1]
# we start by computing t = f.call arr[-1]
# and then q = arr[0..-2].map &f
# then we push t in q (q << t)
# this operation does also change q


class Array
def map &f
if self.empty?
[]
else
print "\nCalling f ... "
t = f.call self[-1]
print "\nOut of f"

print "\nComputing q ... "
q = self[0..-2].map &f
print "q = ", q.to_s

q << t
print "\nPush ... q = ", q.to_s
q
end
end
end


runtest

# On the contrary to the previous map, here we
# stop the execution before computing q so
# when we call the continuation, we re-compute
# q every time. Thus there is no interference between
# the two calls.



print "\n\
###############################\n\
# PERSISTANT MAP : TAIL FIRST #\n\
###############################\n\n"


# This map should always be correct. It does not use
# imperative features. Instead of pushing t into q
# (by q << x), we make a new array (q + [t]) so that
# q will never be modified.


class Array
def map &f
if self.empty?
[]
else
print "\nComputing q ... "
q = self[0..-2].map &f
print "q = ", q.to_s

print "\nCalling f ... "
t = f.call self[-1]
print "\nOut of f ... q = ", q.to_s

r = q + [t]
print "\nAppending ... q = ", q.to_s, ", r = ", r.to_s
r
end
end
end

runtest

# This map does not modify q so there is no
# interferences between the instances of the
# computation


print "\n\
###############################\n\
# PERSISTANT MAP : HEAD FIRST #\n\
###############################\n\n"


# This map should always be correct. It does not use
# imperative features. Instead of pushing t into q
# (by q << x), we make a new array (q + [t]) so that
# q will never be modified.


class Array
def map &f
if self.empty?
[]
else
print "\nCalling f ... "
t = f.call self[-1]
print "\nOut of f"

print "\nComputing q ... "
q = self[0..-2].map &f
print "q = ", q.to_s

r = q + [t]
print "\nAppending ... q = ", q.to_s, ", r = ", r.to_s
r
end
end
end

runtest

# This map does not modify q so there is no
# interferences between the instances of the
# computation.

puts ""
(3-3/3)