The Art of Uniform Interface

Context

After reading how the first, second, third and so on methods were added in the Rails doctrine by DHH, I started to look for ways on how I would actually go about doing something better. Obviously the drawback of defining methods like that is that they are not scalable. But, I see his point of not indexing into an array to get data for some common use cases.

Challenge

How can we come up with a uniform interface that does not result in explosion of method and at the same time achieves ignorance of indexing into an array? Ruby, has values_at method for an array.

> a = [1,2]
 => [1, 2] 
> a.values_at(1)
 => [2] 
> a.values_at(0)
 => [1]

This exposes the knowledge of the indexing of an array. It also knows that is an array. Ruby also has values_at method for hash.

 h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
 h.values_at("cow", "cat")  
 #=> ["bovine", "feline"]

This exposes the knowledge that is an hash. You need to know the key in the hash to get the corresponding value.

Well Defined Interface

I don't care about the type of data structure, just give me the element in the given position.

class Array
  def element(position)
    self[position - 1]
  end
end

p [4,5,6].element(3)

You can just provide the position as the parameter to element method instead of going through the mental mapping of position and index of an array. Similarly, we can define a element method for Hash.

class Hash
  def element(position)
    a = self.to_a[position-1]
    {a[0] => a[1]}
  end
end

h = {a: 1, b: 2, c: 3, d: 4}
p h.element(2)

This returns :

{b: 2}

Both implementation are data structure agnostic. It does not force the developers to map the position of an element and the index in array or knowing the key of a hash.

Principle of Least Surprise

I was surprised when I tried to call element method on Array and Hash and I got errors. Ruby has failed in this case. It only has first and last method defined.

Summary

In this chapter, we saw how we can define a uniform interface that hides the data structure and knowledge required to pass in parameters to a method. This solution eliminates parametric coupling.