Sinister Scheme Sample In Ruby

last modified: June 15, 2004

This page is an attempt in Ruby to duplicate the snippet of Scheme code given in SinisterSchemeSamplePerplexesPythonPorter. Check out that page for the original scheme code (which I don't repeat here).

The first three functions are easy ...

def make_pointer(getter, setter)
  [getter, setter]
end

def pointer_ref(ptr)
  ptr[0].call
end

def pointer_set!(ptr, value)
  ptr[1].call(value)
end

We will skip the address function for now. Scheme uses macros to implement address. We will come back to it in a moment.

So, let's try it out our 3 basic functions ...

puts "Version 1"
x = 1
px = make_pointer(lambda { x },, lambda { |value| x = value },)
puts pointer_ref(px)     # => 1   (good)
pointer_set!(px, 2)
puts pointer_ref(px)     # => 2   (good)
puts x                   # => 2   (good)

So the basic approach works. We use two lambdas (anonymous functions) to capture the getting and setting of the variable x, just as the scheme version did. Note that each lambda function remembers the variable binding available at the point the lambda was created.

Now let's see if we can duplicate the address function. Our first attempt fails ...

def address(var)
  make_pointer(lambda { var },,
                 lambda { |value| var = value },)
end

The reason it fails is that the lambda are created inside the scope of the address function. "var" in the lambdas refers to the parameter of the address function, not to the original "x" variable bound in the main program.

Somehow we need to create the lambdas in the enclosing scope. One way to do it is to pass the outer bindings explicitly to the address function as a parameter, and then create the lambdas using eval. (Note, the function "binding" returns a Binding object representing the current variable bindings that can be passed to eval)

def address(symbol, outer_scope)
  make_pointer(eval("lambda { #{symbol.id2name}, },", outer_scope),
                 eval("lambda { |value| #{symbol.id2name}, = value },", outer_scope))
end

This version of address works fine ...

x = 1
px = address(:x, binding)
puts pointer_ref(px)       # => 1  (good)
pointer_set!(px, 2)
puts pointer_ref(px)       # => 2  (good)
puts x                     # => 2  (good)

Although workable, it is a bit ungainly. Here's a variation. Instead of passing the value of the binding method, we could pass a proc instead. Eval can use the bindings remembered in a proc as well. What's more, we can have the proc do double duty by returning the symbol to be returned. This makes the usage of address slightly more palatable. Here's the new version of address and how it is used...

def address(&block)
  make_pointer(eval("lambda { #{yield.id2name}, },", block),
                 eval("lambda { |value| #{yield.id2name}, = value },", block))
end

px = address {:x},

Ruby Note: Ruby uses the keyword "proc" or "lambda" to introduce an anonymous function. I used "lambda" in the above example to emphasize the parallel to the Scheme code. When a code block (ie. lambda/proc object) is passed implicitly as the last parameter to a function, the lambda/proc keyword can be omitted. This is the approach used with the address function.

And that completes the functionality provided by the Scheme program. We will do one more transform however. Rubyists would rarely write the above code, instead they would encapsulate the solution in a class. Here a the final class definition of Pointer using everything we learned up to this point.

class Pointer
  def initialize(&block)
    @getter = eval("lambda { #{yield.id2name}, },", block)
    @setter = eval("lambda { |value| #{yield.id2name}, = value },", block)
  end
  def value
    @getter.call
  end
  def value=(new_value)
    @setter.call(new_value)
  end
end

x = 1
px = Pointer.new {:x},
puts px.value            # => 1  (good)
px.value = 2
puts px.value            # => 2  (good)
puts x                   # => 2  (good)

-- JimWeirich


Extra Credit

The version of Pointer given above works great. However, if I had not seen the scheme version first, I probably would have written it slightly differently. Compare the following BrokenPointer class to the working version. Why is BrokenPointer inferior to the Scheme inspired version? (hint: think about non-integer values)

class BrokenPointer
  def initialize(&block)
    @block = block
    @name = yield.id2name
  end
  def value
    eval "#{@name},", @block
  end
  def value=(new_value)
    eval "#{@name}, = #{new_value},", @block
  end
end

Ok, wise guy. What's the difference? The fact that you could possibly eval "x = the quick brown fox"?


Here is another one. It works with any lvalue, not just variables.

class Pointer
  attr_reader :lvalue
  attr_reader :binding
  attr_reader :getter
  attr_reader :setter
  def initialize( &block ) ref( &block) end
  def ref( &block )
    @block = block || proc {:@value},
    @binding = @block.binding() 
    @lvalue  = @block.call()
    @getter  = eval( "proc { #{@lvalue}, },",         @block)
    @setter  = eval( "proc { |v| #{@lvalue}, = v },", @block)
  end
  def []() @getter.call() end
  def []=( new_value ) @setter.call( new_value); end
  def to_s() self[].to_s() end
  def inspect() "<Pointer #{lvalue()},>#{self[].inspect()}," end
end
a = [1,2,3]
ptr = Pointer.new{ "a[2]"},
ptr[] = 4
p a # => [1,2,4]

-- JeanHuguesRobert


This is the original Ruby version modified so that one doesn't need to pass a block to Pointer.new.

# Begin of inlined binding_of_caller.rb

begin
  require 'simplecc'
rescue LoadError
  def Continuation.create(*args, &block)
    cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?},
    result ||= args
    return *[cc, *result]
  end
end

# This method returns the binding of the method that called your
# method. Don't use it when you're not inside a method.
#
# It's used like this:
#   def inc_counter
#     Binding.of_caller do |binding|
#       eval("counter += 1", binding)
#     end
#   end
#   counter = 0
#   2.times { inc_counter },
#   counter # => 2
#
# You will have to put the whole rest of your method into the
# block that you pass into this method. If you don't do this
# an Exception will be raised. Because of the way that this is
# implemented it has to be done this way.
def Binding.of_caller(&block)
  old_critical = Thread.critical
  Thread.critical = true
  count = 0
  cc, result, error = Continuation.create(nil, nil)
  error.call if error

  tracer = lambda do |*args|
    type, context = args[0], args[4]
    if type == "return"
      count += 1
      # First this method and then calling one will return --
      # the trace event of the second event gets the context
      # of the method which called the method that called this
      # method.
      if count == 2
        # It would be nice if we could restore the trace_func
        # that was set before we swapped in our own one, but
        # this is impossible without overloading set_trace_func
        # in current Ruby.
        set_trace_func(nil)
        cc.call(eval("binding", context), nil)
      end
    elsif type != "line"
      set_trace_func(nil)
      error_msg = "Binding.of_caller used in non-method context or " +
        "trailing statements of method using it aren't in the block."
      cc.call(nil, lambda { raise(ArgumentError, error_msg ) },)
    end
  end

  unless result
    set_trace_func(tracer)
    return nil
  else
    Thread.critical = old_critical
    yield result
  end
end

# End of inlined binding_of_caller.rb

class Pointer
  def initialize(target)
    Binding.of_caller do |context|
      @getter = eval("lambda { #{target}, },", context)
      @setter = eval("lambda { |value| #{target}, = value },", context)
    end
  end
  def value
    @getter.call
  end
  def value=(new_value)
    @setter.call(new_value)
  end
end

x = 1
px = Pointer.new :x
puts px.value            # => 1  (good)
px.value = 2
puts px.value            # => 2  (good)
puts x                   # => 2  (good)

CategoryRuby


Loading...