My Ruby Learning Path: Constructing the Constructor

When I started learning Ruby, all of the code samples that I looked at used positional arguments in the constructor, such as with the following code:

  def initialize(id, type, price)
    @id, @type, @price = id, type, price
  end

By positional arguments, I mean that the first argument must pass the id, the second must pass type, and the third must pass price. Having worked with C and Java, this feels very familiar, and so that is how I did it in my own code. However, when I posted my code samples to the dev@ruby.netbeans.org alias, a few developers suggested that I use a hash, like in the following code.


  def initialize(attributes)
    @id = attributes['id']
    @type = attributes['type']
    @price = attributes['price'].to_f
  end

I can see some advantages of using a hash instead of requiring arguments in a certain order. For one, with positional arguments, there is the possibility of passing the values in the wrong order. Another advantage of a hash is that if you add more attributes to a class, the constructor's signature does not change.

There was a bit of a debate about which version to use. Some developers like the constructor with positional arguments because it clearly documents the intent of the class. Others feel that keyword arguments, along with RDoc, make the code more clear than depending on argument ordering to convey meaning. Comparing the two types of method invocations, I have to agree that the hash seems easier to read.

  Item.new 'id' => 'ABC', type' => 'book',  'price' => 17

  Item.new 'ABC', 'book', 23

How about you? What are your preferences? Do you carry it even further? Would you ever consider doing something like this?


  def initialize(&block)
    instance_eval &block
  end

  item = Item.new do
    self.id = 'ABC'
    ...
  end

Do you know of scenarios where it might be appropriate to use code like this?

  def initialize(attributes)
    @attributes = attributes.with_indifferent_access
  end
 
  def method_missing(symbol, \*args)
    @attributes[symbol] ? @attributes[symbol] : super
  end

Charles Nutter has blogged about how to extend Class with a field-initializing 'new' method. Jay Fields posted in his blog code forcreating constructors that can take either positional arguments or a hash. For someone starting out with Ruby, I find these latter methods a bit uncomfortable, but it does save a bit of finger work. What do you think?

Comments:

If hash notation translates into "slow code" then perhaps you'll want to avoid it.

Posted by Nico on November 27, 2007 at 08:25 AM PST #

Good point Nico. Does anyone have any stats on how much hash notation use can affect a web site with thousands of hits and hundreds of classes. How about with JRuby. Does the server make a difference?

Posted by Diva #2 on November 28, 2007 at 01:34 AM PST #

Rails uses hash-initialization extensively and everywhere, so it seems unlikely that it's a big performance hit (since if it was, Rails would crawl, which it doesn't).

As for personal preference, I usually decide depending on the method at hand. If it's something simple that semantically requires one or two arguments, such as a functional paradigm or just something without too many side-effects, I prefer order-notation. If it's configuration or the options don't have a natural logical order, I prefer hash-notation.

An example: A 3D vector class (Vector3) has 3 attributes, one for each axis coordinate. One could make a constructor like this:

Vector3.new(:x => 1, :y => 0.5, :z => 7)

But nobody has ever written a 3D vector with coordinates in any other order than lexical X, Y, Z, so all this typing is better saved:

Vector3.new(1, 0.5, 7)

However, in Rails you have the link_to helper function used in views to link to a controller action. There's no natural order for the controller name, action name, and the parameters they take, so it's a good thing to have them named:

link_to(text, :controller => 'my_controller', :action => 'do_it', :id => 42)

As for block initializers... I haven't really seen them shine yet. I tend to think it's a bad idea unless your class actually uses the block in other places than the initializer, such as Proc.

- Simon

Posted by Simon Ask Ulsnes on November 29, 2007 at 02:27 PM PST #

Hi Simon,
Your rule of thumb about size and whether the arguments follow a natural logical order or not makes good sense and is easy to remember and follow. I am curious about functions that have a small number of arguments but "too many side effects." Do you have an example of such a function that illustrates why, in that situation a hash is better than positional arguments?

Posted by Diva #2 on November 30, 2007 at 12:06 AM PST #

Post a Comment:
Comments are closed for this entry.
About

divas

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today