History always repeats itself and sometimes that's good. Here's a second chance to be part of it: tmdtc.com

Saturday, 11 October 2008

Ruby flexibility to the rescue

In the development of UploadForMe, I had a chain of methods called, each of which possibly returning nil. I really didn't want to check at each step if the returned value was nil, as I would have ended up with something like

if (o!=nil and o.first_call!=nil and o.first_call.second_call!=nil)

I quickly created a class which would help me get around this problem, and I think (I haven't checked further, honnestly) this goes in the direction of monads, which are well known in Haskell.

The solution is to have a class of which an instance is returned in place of nil. This instance responds to all methods by returning an instance of the same class, in turn responding to all methods. However such an instance responds to the method #nil? with true.

class SimpleMonad
def initialize(h={:is_nil => true})
@is_nil = h[:is_nil]
end

def method_missing(m, *args)
return SimpleMonad.new
end
def nil?
@is_nil
end
end

And I'm using this class as return value in a class derived from Hash. method_missing returns either the value in the hash for which the key is the method name just called, or an instance of the class above:

class ServicesKeys < Hash
def method_missing(m, *args)
if keys.include?(m)
return self[m]
else
return SimpleMonad.new
end
end
end


That way, we can chain calls as we want:

#define the object we will work on
h = ServicesKeys[ :uploadforme => ServicesKeys[:login => 'rb', :username => 'rb']]

#we can easily access the values in there:
h.uploadforme.login
=> "rb"
#if we try to access values for an undefined service, we don't get in trouble:
h.myowndb.login.nil?
=> true


You have to be cautious in naming your keys to avoid clashes, but in my case this is an acceptable trade-off.

There might also be better and more advanced solutions out there. Don't hesitate to mention them in the comments.

No comments: