Wednesday, February 6, 2008

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

This is the second of a two-part series of posts about the way different languages allow you to handle a call to method that doesn't exist. For this part I'm going to show Python, Objective-C, Haxe, ActionScript and Perl . At the end a note on languages not covered.


Python

In Python, this feature is available by implementing the __getattr__(self, name) method. This method is used to handle the access to a attribute.

The CsvFileEntry class code looks like this:


class CsvFileEntry(object):
def __init__(self,headers,contents):
self.headers = headers
self.contents = contents

def __getattr__(self, name):

if (self.headers.has_key(name)):
return self.contents[self.headers[name]]
else:
raise AttributeError,name



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


class CsvFile(object):
contents = []
headers = {}
def __init__(self, filename):
...

def entries(self):
for e in self.contents:
yield CsvFileEntry(self.headers,e)


A use of these classes:


f = csvfile.CsvFile('testfile.csv')
cars = csvfile.CsvFile('cars.csv')


for e in f.entries():
print "%s , %s\n" % (e.Name,e.Age)

for e in cars.entries():
print "%s , %s\n" % (e.Model,e.Year)


As someone commented in the previous post, __getattr__ could also return a function. A nice example of this is available here: Python's getattr.

Objective-C

The method forwarding capabilities of Objective-C could be used to implement this feature.

For this example I'm using GNUstep. A nice description of the forwaring mecanism using GNUstep is available from its documentation: 5.3 Forwarding.


The definition of the CsvFileEntry class is the following:

Declaration:



#include <Foundation/Foundation.h>

@interface CsvFileEntry: NSObject
{
NSArray* entryContents;
NSDictionary* headers;
}
- (id)init:(NSArray*)contents headers: (NSDictionary*)theHeaders;
- (NSString*) getField:(NSString*)name;
- (void) forwardInvocation: (NSInvocation*)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;

@end


Implementation:


@implementation CsvFileEntry

- (id)init:(NSArray*)contents headers: (NSDictionary*)theHeaders
{
entryContents = contents;
headers = theHeaders;
}

- (NSString*) getField:(NSString*)name
{
if ([headers objectForKey: name] != nil) {
NSNumber* index = [headers objectForKey: name];
NSString* result = [entryContents objectAtIndex: [index intValue]];
return [entryContents objectAtIndex: [index intValue]];
} else {
return nil;
}
}

- (void) forwardInvocation: (NSInvocation*)invocation
{
NSString* a = NSStringFromSelector([invocation selector]);
[invocation setArgument: &a atIndex: 2];
[invocation setSelector: NSSelectorFromString(@"getField:")];
return [invocation invokeWithTarget:self];

}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
if([headers objectForKey: NSStringFromSelector(sel)] != nil) {
NSMethodSignature* sig;
sig = [[self class]
instanceMethodSignatureForSelector:
NSSelectorFromString(@"getField:") ];
return sig;
} else {
return [super methodSignatureForSelector: sel];
}

}
@end


The methodSignatureFromSelector method returns information about the method to be invoked. Here what we do is to return the information of the getFieldMethod. The forwardInvocation method forwards the invocation to the getField method and sets the name of field as its first argument (arguments starts at index number 2).


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


@interface CsvFile: NSObject
{
NSArray* contentsData;
NSDictionary* headers;
NSString* fileName;
}
- init: (NSString*)filePath;
- (NSArray*) entries;
@end



@implementation CsvFile
-init: (NSString*)filePath
{
...
}

- (NSArray*) entries
{
int contentsSize = [contentsData count];
NSMutableArray* entriesArray = [NSMutableArray arrayWithCapacity: contentsSize];

int i;
for(i = 0;i < contentsSize;i++) {
[entriesArray
addObject:
[[[CsvFileEntry new]
init: [contentsData objectAtIndex: i]
headers: headers ]
autorelease]];
}
return entriesArray;
}
@end


A use of these classes is the following:


...
CsvFile* csv = [[CsvFile alloc] init: @"testfile.csv"];
CsvFile* cars = [[CsvFile alloc] init: @"cars.csv"];

int i;
NSArray* entries;
NSLog(@"--------------");

entries = [csv entries];
for( i = 0;i < [entries count];i++)
{
CsvFileEntry* entry = [entries objectAtIndex: i];
NSLog(@"Name: %@ Score: %@",[entry Name], [entry Score]);
}

NSLog(@"--------------");

entries = [cars entries];
for( i = 0;i < [entries count];i++)
{
CsvFileEntry* entry = [entries objectAtIndex: i];
NSLog(@"Model: %@ Year: %@",[entry Model], [entry Year]);
}
...


A nice experiment will be to try this code in a Mac environment.

Haxe

Haxe is an interesting language that comes with a compiler with backends for Javascript, Flash and the Neko virtual machine. Haxe provides this functionality with the __resolve method a description of this mecanism can be found here: missing_method, the haxe way.

The implementation of the CsvFileEntry class is the following:


class CsvFileEntry implements Dynamic<Array<Dynamic>->Dynamic>
{
private var headers : Array<String>;
private var contents : Array<String>;
public function new(aHeaders : Array<String>, aContents : Array<String>) {
headers = aHeaders;
contents = aContents;
}

function __resolve( name : String ) : Array<Dynamic>->Dynamic {
var index = getHeaderPosition(name);
var contents = this.contents;
if(index != -1) {
return function(args:Array<Dynamic> ):Dynamic {
return contents[index];
};
} else {
return null;
}
}
...
}


The implementation of the CsvFile (without the file load part):


class CsvFile {
var fileName : String;
var contents : List<Array<String>>;
var headers : Array<String>;

public function new(aFileName:String) {
...
}

public function entries() : List<CsvFileEntry> {
var headers = this.headers;
return contents.map(
function(data:Array<String>) :CsvFileEntry {
return new CsvFileEntry(headers,data);} );
}
}


A use of these classes is the following:


...
var f:CsvFile = new CsvFile("testfile.csv");
var cars:CsvFile = new CsvFile("cars.csv");


for (e in f.entries()) {
var v:String = e.Name([]);

neko.Lib.print(v);
neko.Lib.print("<br/>");
}
for (c in cars.entries()) {
var v:String = c.Model([]);
neko.Lib.print(v);
neko.Lib.print("<br/>");
}
...


ActionScript

ActionScript 3 provides this functionality by using inheriting from the Proxy class. An excellent description of this mecanism can be found in method_missing in ActionScript 3/Flex.

The implementation of the CsvFileEntry is the following:


dynamic public class CsvFileEntry extends Proxy
{

private var headers: Dictionary;
private var contents: Array;
public function CsvFileEntry(headers : Dictionary, contents : Array )
{
this.headers = headers;
this.contents = contents;
}

flash_proxy override function callProperty(method: *, ...args): * {
if (headers[method] == null)
{
throw( new Error("Method not found: "+method.toString()));
}
else
{
var i:int = headers[method];
return contents[i];
}
}
}
}


The CsvFile class without the file loading code:


public class CsvFile
{
private var file : File;
private var contents : ArrayCollection;
private var headers: Dictionary;
public function CsvFile(file:File)
{
...
}
public function getEntries() : ArrayCollection
{
var result : ArrayCollection = new ArrayCollection();
for each (var entry:Array in contents) {
result.addItem(new CsvFileEntry(headers,entry));
}
return result;
}
}



A use of these classes:


var f:CsvFile = new CsvFile(new File("testfile.csv"));
var cars:CsvFile = new CsvFile(new File("cars.csv"));
for each( var e:CsvFileEntry in f.getEntries()) {
aList.addItem(e.Name());
}
for each( var c:CsvFileEntry in cars.getEntries()) {
aList.addItem(c.Year());
}


Perl


As commented in the previous post, Perl provides this functionality with the AUTOLOAD method. A nice description is provided here: AUTOLOAD: Proxy Methods


Implementation of the CsvFileEntry class is the following:


package CsvFileEntry;
use strict;
use Carp;
our $AUTOLOAD;


sub new {
my $self = {};
my $class = shift;

my %headers = %{$_[0]};
my @data = @{$_[1]};

$self->{Headers} = \%headers;
$self->{Data} = \@data;

my $al = scalar(@{$self->{Data}});

bless($self,$class);
return $self;
}

sub getfield {
my $self = shift;
my $key = shift;
my %headers = %{$self->{Headers}};
my @data = @{$self->{Data}};
return $data[$headers{$key}];
}

sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
$name =~ s/CsvFileEntry:://;

my %headers = %{$self->{Headers}};

unless (exists $headers{$name}) {
croak "Cannot access member $name";
}

my $key = $headers{$name};
my @data = @{$self->{Data}};

return $data[$key];
}

1;


Implementation of the CsvFile class is the following:


package CsvFile;
use strict;
use CsvFileEntry;

sub new {
...
}
...
sub content {
my $self = shift;
my @entries = map {CsvFileEntry->new($self->{Headers},\@{$_})} @{$self->{Content}};
return @entries;
}
1;



A use of this code is the following:


use CsvFile;

$f = CsvFile->new("testfile.csv");
$cars = CsvFile->new("cars.csv");

foreach $person ($f->content) {
my $name= $person->Name;
my $age = $person->Age;
print "$name $age \n";
}
foreach $car ($cars->content) {
my $model= $car->Model;
print "$model\n";
}




Languages not covered.

Some languages were not covered in these posts. For example in the previous post schlenk commented that Tcl provides this functionality with the unknown proc.

Also EcmaScript 4 seems to provide this functionality, in slide 14 of Tamarin and ECMAScript 4.

I hope I can cover more on these languages in the future.

Code for this post can be found here.

2 comments:

pjkeane said...

And in PHP:

http://us2.php.net/manual/en/language.oop5.overloading.php

using __call(),__get(),__set()

"magic" methods

Cheba said...

The Haxe variant is only usable with Neko. I don't see it working in compiled versions. At least in JS ones.