Monday, February 4, 2008

Handling a call to a missing method in different languages, Part 1

This is the first of a two-part series of posts about the way different languages allow you to handle a call to method that doesn't exist. I'll try to implement a little example with each of these languages. For this part I'm going to show Ruby, Smalltalk, Groovy and Boo.

The doesNotUnderstand message


Although this feature is called by many names, the origin may be the doesNotUnderstand message of Smalltalk. From its Wikipedia entry:


When an object is sent a message that it does not implement, the virtual machine sends the object the doesNotUnderstand: message with a reification of the message as an argument. The message (another object, an instance of Message) contains the selector of the message and an Array of its arguments...


This mecanism could be used for many tasks, from debugging to creating proxies. A nice reflection on this is The Best of method_missing and also in the reflection section of Smalltalk Wikipedia entry.

One of the most interesting uses is in the implementation of Ruby on Rails's ActiveRecord. In Under the hood: ActiveRecord::Base.find there's a nice description on how the find_... method is implemented using method_missing.


The example


For these posts I'm going to implement a little example of two classes that provides access to a CSV file by using this mecanism. For example given that we have the following file called "cars.csv" (taken from here):


Year,Make,Model
1997,Ford,E350
2000,Mercury,Cougar


and another called "scores.csv"


Name,Age,Score
Luis,31,1.8
John,35,1.9
Paul,25,1.6


For simplicity we assume that not double quotes are used.

Given that the first row are the headers, we can use this class to have access to the data of each record using the name of the header as if it was a member of the class. For example:


cs = CsvFile.new("cars.csv")
ps = CsvFile.new("scores.csv")

cs.each do|c|
puts c.Model
end

ps.each do|p|
puts p.Name
end


The basic strategy to create the example is to create two classes CsvFile and CsvFileEntry. One to represent the file and the other to represent each entry.

In general, the contents of the file will be loaded as an list of lists. The headers will be loading in a dictionary associated with its position.


The languages


For these posts I tried to find programming languages that have direct support for this feature. For most of these programming languages, the presented snippet is my first program in it, so please let me know if I missed something.

Ruby

In Ruby the method_missing method provides this functionality.

The method signature is the following:


obj.method_missing( aSymbol [, *args ] ) -> anObject


In our little example the:


class CsvFileEntry
def initialize(headers,content)
@headers = headers
@content = content
end
def method_missing(method_name,*args)
if (@headers.has_key? method_name.to_s) then
return @content[@headers[method_name.to_s]]
else
raise "Method not found #{method_name}"
end
end
end


Implementation for the CsvFile class is the following(without the file loading code):


class CsvFile
attr_reader :headers
attr_reader :content
def initialize(filename)
@filename = filename
@headers = Hash.new
@content = []
...
end

def entries
return content.collect {|c| CsvFileEntry.new(@headers,c)}
end
end


In the implementation of method_missing, the method_name argument is converted to string and used to lookup the name of the requested field. The headers instance variable contains a Hash with the positions of each header and the contents variable contains the contents of the entry.

Now we can use these classes as follows:


f = CsvFile.new("testfile.csv")
cars = CsvFile.new("cars.csv")

f.entries.each { |e| puts "#{e.Name} #{e.Age}" }

f.entries.each do |e|
puts e.Name
end

print "-------\n"

cars.entries.each do |c|
puts c.Model
puts c.Year
end


Smalltalk

As described above, in Smalltalk we have to implement the doesNotUnderstand method. This method receives a an instance of the Message class . This class contains information about the requested method and arguments.

The implementation of the CsvFileEntry class looks like this:


Object subclass: #CsvFileEntry
instanceVariableNames: 'headers contents'
classVariableNames: ''
poolDictionaries: ''
category: 'Langexplr-Classes'!


doesNotUnderstand: aMessage
| index |
^(headers includesKey: aMessage selector)
ifTrue:
[index := headers at: (aMessage selector).
contents at: index.]
ifFalse: [super doesNotUnderstand: aMessage].



initializeWith: theContents headers: theHeaders
headers := theHeaders.
contents := theContents.
^self.


The implementation of the CsvFile class(without the file loading code) looks like this:


Object subclass: #CsvFile
instanceVariableNames: 'fileName contents headers'
classVariableNames: ''
poolDictionaries: ''
category: 'Langexplr-Classes'


getContents
^contents.


getEntries
^(contents collect:[:e | (CsvFileEntry new) initializeWith: e headers: headers ]).

initializeWithFileName: aFileName
...
^ self.


An example of a use of these classes is the following:


scores := CsvFile new initializeWithFileName: 'testfile.csv'.

cars := CsvFile new initializeWithFileName: 'cars.csv'.

cars getEntries do:
[:entry|
Transcript show: (entry Model)].

scores getEntries do:
[:entry|
Transcript show: (entry Name)] .


Groovy

Groovy provides this feature for methods and properties. A description of this mecanism can be found in Using methodMissing and propertyMissing
. A nice example of a use of this mecanism is the GORM's Domain Class Querying.

In Groovy we need to implement the def propertyMissing(String name) method or the def methodMissing(String name,args) to handle a property or method access. The following CsvFileEntry implementation uses overrides both methods to provide property access and getter method(getXXXXX) access.


public class CsvFileEntry {
private HashMap headers;
private String[] contents;

CsvFileEntry(HashMap headers,String[] contents) {
this.headers = headers;
this.contents = contents;
}
def propertyMissing(String name) {
if (headers[name] != null) {
return this.contents[this.headers[name]];
} else {
throw new RuntimeException("Property not found ${name}");
}
}
def methodMissing(String name,args) {
def getterNameResult = name =~ /get(.*$)/;
if (getterNameResult.matches() &&
headers[getterNameResult.group(1)] != null) {
return this.contents[this.headers[getterNameResult.group(1)]];
} else {
throw new RuntimeException("Method not found ${name}");
}
}
}


The implementation for the CsvFile class is the following(without the file loading part):


span class="srckeyw">public class CsvFile {
def HashMap headers;
def contents;

CsvFile(String fileName) {
...
}

def entries() {
def result = []
for ( c in contents) {
result.add(new CsvFileEntry(headers,c));
}
return result
}
}


A use of this code looks like this:


def scores = new CsvFile("testfile.csv")
def cars = new CsvFile("cars.csv")


for ( e in scores.entries() ) {
println("${e.Name} --> ${e.getAge()}")
}

for ( c in cars.entries()) {
println(c.getModel());
println(c.Year)
}


Boo

In Boo this functionality is available by implementing the IQuackFu interface. A nice explanation of this feature is available from: If it walks like a duck and it quacks like a duck . Also Dynamic Inheritance - fun with IQuackFu provides more information . A very nice example called XmlObject.boo (available from Boo source distribution) shows how IQuackFu is used to easy the access to Xml trees.

The IQuackFu interface contains the following members:

  • def QuackInvoke(name as string, args as (object)) as object: for method calls

  • def QuackGet(name as string) as object: for property access

  • def QuackSet(name as string, value) as object: for property assignment




Since Boo allowed handling both method and property access I created two ways to access the data: by using the name of the header as a property or a call to a method called GetXXXXX.

The CsvFileEntry implementation is the following:


class CsvEntry(IQuackFu):
content as (string)
headers as Hash
public virtual def constructor(aContent as (string),aHeaders as Hash):
content = aContent
headers = aHeaders

def QuackInvoke(name as string, args as (object)) as object:
r = /Get(?<name>.*$)/.Match(name)

if (r.Success and headers.ContainsKey(r.Groups["name"].Value)):
index = headers[r.Groups["name"].Value]
return content[index]
else:
raise InvalidOperationException("Method ${name} not found")


def QuackSet(name as string, value) as object:
pass

def QuackGet(name as string) as object:
if(headers.ContainsKey(name)):
index = headers[name]
return content[index]
else:
raise InvalidOperationException("Property ${name} not found")


The CsvFile implementation (without the file loading part) :


class CsvFile():
headers = {}
content = []
public virtual def constructor(fileName as string):
...

public def Entries():
for e as (string) in content:
yield CsvEntry(e,headers)



A use of this class:


csvF = CsvFile("testfile.csv")
cars = CsvFile("cars.csv")


for e in csvF.Entries():
print e.Name


for c in cars.Entries():
print c.GetModel()



Code for this post can be found here.

For the next post the Python, Objective-C, Haxe, ActionScript and Perl examples will be presented.

8 comments:

elarson said...

In Python there is getattr and hasattr that can be used to get the reference to either a function or an attribute.

For example:

if hasattr(anobj, 'do_func'):
func = getattr(anobj, 'do_func')
return func(*args, **kw)

When you couple this with the fact you can pass around functions (ie add the "func" reference above to a list or dict), you find a very powerful construct.

Luis Diego Fallas said...

Thanks for the information. I'm going to include it in the second part.

schlenk said...

In Tcl this is handled via the [unknown] proc, or more recently via the [namespace unknown] proc per namespace.
Some examples can be found here, including rather radical stuff:
http://wiki.tcl.tk/795
http://wiki.tcl.tk/12790

The usual Tcl OO extensions (Snit, XOTcl) use similar schemes to provide unknown per object or per class.

Luis Diego Fallas said...

Thanks for the links and the information! I didn't know about that Tcl feature.

ktvoelker said...

Perl also has this functionality:

package Foo;
sub AUTOLOAD {
print "You called $AUTOLOAD(@_)\n";
}
Foo->bar(1, 2, 3);

The arguments are in @_ as usual; the name of the method called is in $AUTOLOAD.

Luis Diego Fallas said...

Thanks, I'll try to include Perl in the next part.

Ramon Leon said...

You can simplify that Smalltalk, dnu just throws an exception by default so this works.

doesNotUnderstand: aMessage
^contents at:
(headers
at: (aMessage selector)
ifAbsent: [super doesNotUnderstand: aMessage])]

Luis Diego Fallas said...

That looks very nice, and reads much better!.