Skip to content

Duck Typing and "Roles", Continued

by jjdonald on May 7, 2009

My previous article talked a bit about Perl’s new “roles” method for managing typing.  In the article, I tried to address some of the criticism levied against duck typing, and in doing so perhaps misrepresented Perl Roles as being “rigid”.

Another blogger by the name of Sam Crawley gives an overview of the way that roles provide more flexibility in a recent post, however there is still a significant difference in approaches.

Duck Typing and Reflection

In both the original post on duck types and Perl Roles, as well as Sam’s rebuttal of my previous article, there is mention of the “can” method.  This method is used like:

$dog_or_tree->can( 'bark' ); 

This method tests to see if an object has the requisite field (presumably so that you can call a corresponding method, or access a property), and is very similar to the intent of duck typing.  However, it’s important to note that using this “can” method is not the same as duck typing.  The method is actually what’s known as a runtime class reflection, and not a compile-time type check.  This is an important distinction, because  true duck-typing will make the compiler check the types once before compilation, while reflection requires that the types be checked every time the code is run.  On many platforms (VM’s such as Adobe’s AVM2) reflection calls are quite slow, and should be avoided.

It seems that both authors are conflating these approaches, and unfairly criticizing duck typing for the inefficiencies of managing types through reflection:

If you want to get stricter, and if your language supports this, you can even check the arity or types of the allowed signatures of the method — but look at all of the boilerplate code you have to write to make this work. That’s also code to check only a single method.

You don’t need to insert “boilerplate code” in your classes for conventional duck typing approaches.  You write one “typedef” of required methods, as in a haXe example:

typedef Duck ={
   function walks(location:Position, direction:Vector<Float>):Waddle<Step>;
   function talks(say:String):Quack<Sound>;
}

And typically put it somewhere in your compiler class path.  Now you can accept “Ducks” as arguments anywhere, and they can be checked by the compiler.  From a typedef, we can assert that the Duck walks and talks appropriately (producing “Waddle” and “Quack” collections of “Steps” and “Sounds”).  The arity, return, and argument types let us describe the behavior in great detail.  The level of discrimination that is available only depends on the specificity of the classes, and any duck type assertions occur externally to class specific code.  After we write our Main classes, and create the Duck typedef, we could perhaps differentiate between different ducks, such as those that “molt() : Void”, and we wouldn’t need to update any existing class code outside of creating a new typedef:

typedef MoltingDuck = {
     > Duck,
     function molt():Void
}

Now we can make sure we’re dealing with molting ducks, that behave exactly like ducks in every other way.

Roles vs. Duck Typing, revisited

However, for a final comparison of Roles and Duck Typing, it is perhaps fair to say that Roles are more powerful in a sense.  Roles actually cover a lot of ground, it’s just different ground than what Duck Typing covers.  Roles encompass two major forms of class specification:  interfaces and mixins. The former lets you define methods and properties that must be implemented by the host class, while the latter define methods and properties that are implemented by the Role itself (the methods are specified in the Role description)  In this way, you can even use Roles as building blocks for class functionality in lieu of inheritance.  It seems that this is a pretty revolutionary way to approach OOP, so more power to them.

However, the  type checking performed by Roles is more rigid and structured than duck typing, at least in terms of what happens at the compiler level. You still must declare a Role inside of a class description (a la interfaces) for compile time checking.  For interpreted languages, it may not be that important to distinguish between type checking that occurs at the compiler vs. the checks that occur at run-time.  However, this distinction can be very important for other languages.

I can appreciate the need for articles to promote the idea of Roles within and without the Perl community, but I think it can also be done without unfairly criticising other approaches.  Despite the inaccuracies, the discussion of Roles has gotten me interested in Perl again, and nothing else has done that in years.

From → haXe

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS