Exploring copy, swap, and argument passing in Ruby

Modifying a variable of type Fixnum passed to a method

I had a need to pass in a variable of Fixnum type to a method and use the method to update its value.    This wasn’t quite as straightforward as I had hoped.

The first question I needed to answer was how arguments were passed into methods – call by reference or call by value?

I found a lot of discussion about this in post such as void foo(int &x) -> Ruby? Passing integers by reference?  and Are arguments passed to methods by reference or value?

My takeaway was that variables are passed by value, but that those variables are references to objects.    With an object such as an Array or Hash, you can modify elements of the object using the passed (immutable) reference.  The problem with a Fixnum, as pointed out in the post ruby and references. Working with fixnums, is that a Fixnum object lacks any methods that allow you to mutate the value.

Using a binding to solve the problem

I found the solution to my problem in the post ‘pass parameter by reference’ in Ruby?

def func(x, bdg)
  eval "#{x} += 1", bdg
end

a = 5
func(:a, binding)
puts a # => 6

According to the Ruby documentation, “Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use.”

The example above using a binding allows the method to gain access to the variable  from the context of the caller.   A symbol is passed to the method instead of the variable name.   If you tried to pass in “a” instead of “:a” you would get a syntax error referring to the illegal assignment “5 += 1”.

What is a symbol?  An object.   According to the Ruby documentation  “Symbol objects represent names and some strings inside the Ruby interpreter”.

Exploring binding with swap

The binding object is quite powerful and can be used to solve other problems such as swap.

Suppose you want to swap two objects of any type.  The easiest way to do this is with code such as:

a, b = b, a

How could this be accomplished without using a return value?   I ran across a nice discussion on bindings at Variable Bindings in Ruby

Inspired by this post I came up with the following method:

def universal_swap(var_a, var_b, vars)
  a = vars.eval "#{var_a}"
  b = vars.eval "#{var_b}"
  vars.eval "temp = a"

  vars.eval "a = b"
  vars.eval "b = temp"
end

# a, b are any two objects
universal_swap(:a,:b, binding)

A deep copy alternative using binding

The easiest and fastest method that I have found to perform a deep copy from one object to another is using the marshaling library:

def marshal_deep_copy(o)
  Marshal.load(Marshal.dump(o))
end

b = marshal_deep_copy(a)

An alternative can be created using binding and eval:

def deep_copy(x, var_y, vars)
  vars.eval "#{var_y} = #{x}"
end

# Perform a deep copy of a to b
# If object b already exists, you can pass in simply :b as second argument,
deep_copy(a,b=:b,binding)

The eval in deep_copy ends up creating a new object.  You can confirm this by examining the object_id:

a = Array[1,{a:"apple",b:"banana"},3]

puts "#{a.object_id}" # -606878778

deep_copy(a,b=:b,binding)

puts "#{a.object_id}" # -606878778
puts "#{b.object_id}" # -606879428 New object b!

a[1][:b] = "cat"  # Does not affect Array b

puts a.inspect # [1, {:a=>"apple", :b=>"cat"}, 3]

puts b.inspect # [1, {:a=>"apple", :b=>"banana"}, 3]

Performance?

I used module benchmark to test marshaling versus my deep_copy method.  My method took a little more than twice the time as the marshaling method.

That’s okay – just a proof of concept 🙂

Last bit on binding

As an aside, I found an alternative to getting the caller’s binding inside a method without explicitly passing it:

def deep_copy_with_block(x, var_y, &block)
  vars = block.binding

  vars.eval "#{var_y} = #{x}"
end

deep_copy_with_block(a,b=:b){}

Here we pass in a block and obtain the binding from this block (which operates in the context of the caller). I think the explicit passing of binding is clearer, but this was interesting nevertheless.

Advertisements

One response to “Exploring copy, swap, and argument passing in Ruby

  1. For doing deep copies of your Ruby objects, you might consider the deep_dive gem.

    https://rubygems.org/gems/deep_dive

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s