More Roles versus Duck Typing

| 2 Comments

I received more feedback on Perl roles versus Duck Typing than any other entry in my series on Perl roles so far. Much of this feedback asked very good questions and pointed out places where I'd assumed that theory or implications were clear. Before I compare Perl roles to any other mechanism, it seems useful to clarify more about what I meant in my duck typing entry.

If you haven't also read The Why of Perl Roles, start there. Those design goals are important to understanding the benefits and drawbacks of other approaches.

Can't You Just Design Your Hierarchy Correctly?

Ryan Funduk wrote in Ruby you can solve a lot of this by simply designing your class hierarchy appropriately..

That's true -- sometimes. If your data model fits nicely into a singly-rooted hierarchy, and if you can add variants at the leaves of the inheritance tree, inheritance works just fine.

Sometimes that's not possible. Ruby (and Perl and Python) make that process much easier. If your language supports multiple inheritance, you can have multiple parents (but as Perl Roles versus Inheritance describes, multiple inheritance has its own complications).

Can't You Code More Carefully?

paddy3118 pointed out, quite correctly, there's no substitute for knowing your code. I agree!

I worry less about my code than I do about the code of other people. When I release code to the CPAN, I try to write robust code that's sufficiently generic (or polymorphic) that I don't prevent smart people from doing smart things I never anticipated. I respect my published interfaces (and theirs). I don't forbid subclassing or specialization. I respect encapsulation. Sometimes I even install tests that they can adapt and specialize if they adopt and specialize my code.

Of course I also try to write safe and robust code.

One of the mechanisms by which I try not to forbid other people from doing something I anticipated is to avoid forcing them to inherit from my classes. I could sprinkle isa() checks throughout my code, but then they'd have to lie by overriding isa() or inherit when they really wanted to delegate or reimplement or compose.

I could throw can() checks to make sure that whatever they pass in supports a method of the appropriate name, but that leaves the code vulnerable to the false cognate problem.

I could ignore all of those possibilities and tell people that if the code breaks, they get to sweep up the pieces.

I prefer something safer and less prescriptive. If you pass my code an object which you've explicitly marked as performing a role we both understand, great! My code will do its best to work with it in a way that we both understand.

There may be bugs -- especially bugs of mutual misunderstanding of what that role implies -- but the role system will protect us against typos and incompleteness and collisions, and that's something you don't get from duck typing.

What if someone gets a role wrong?

Then your program has bugs, the same way as if someone gets inheritance wrong.

What if a role isn't a sufficient test of interoperability?

Then your program has bugs, the same way as if someone gets inheritance wrong.

What if two implementations of a role are incompatible in a given context?

I'm not sure what this means. If you compose two roles into a class and both roles supply a method of the same name, the role system will throw a compilation error. It won't try to disambiguate them; it cannot. You will have to do so.

This often means figuring out the context in which your class will perform one method or another and writing your own method which can dispatch appropriately.

Dog/Tree is a Dumb Example

Justin Donaldson wrote two longer, thoughtful responses: Duck Typing and "Roles" in Object Oriented Programming and Duck Typing and "Roles".

Justin's right; the "A dog can bark() and a tree has bark() example is silly." That's part of the reason it's a catchy Perl cliché. It's a deliberately simple, deliberately dumb example you can explain in a sentence or two to demonstrate a very real principle that appears in code in much more subtle ways: similar words do not always mean similar things.

If you prefer an example from spoken languages, never tell a native Spanish speaker that you're embarazada when you mean that you're embarrassed.

Can't You Check Multiple Methods?

Justin pointed out that if checking for the existence of a single method on an invocant of unknown type is insufficient to determine its type equivalence, checking for multiple methods is safer. I agree -- but then you have to check for multiple methods (likely with your runtime reflection system), and you've only reduced your uncertainty of false cognates.

I'm sure you can see where this is going. Perhaps after you've checked three methods you've reduced your uncertainty sufficiently, at the cost of multiple lines of code and multiple runtime checks.

Perl Roles force you to specify roles explicitly

Yes, they do. To perform a role, add does Name to your class declaration.

I joke. It's not quite that easy. For that to work, you must have identified a role in your system somehow, either by declaring it explicitly (similar to how you'd declare a class, except using the role keyword instead of the class keyword, for a savings of one character) or by declaring a class and treating that class as a role (an ability you get for free).

If your program is small and you're not worried about future extension or the cost of false cognates or duck typing problems, don't use roles. Don't use types. Don't use inheritance. That's perfectly acceptable. They're most valuable in larger systems where you do want pervasive polymorphism and extensibility without worrying about the drawbacks of ad hoc and unspecified invocations.

Checking methods is silly anyway; no one does that!

If duck typing's drawbacks weren't a problem, you wouldn't see Wikipedia on Duck Typing recommending the use of Python try/catch blocks for invoking methods on unknown invocants. If the method call cannot possibly fail, there's no reason to catch the "Hey, this method call failed! Hm!" exception.

Similarly, using can() to check that an invocant supports a potential method is a well-worn idiom in Perl in the same way that checking responds_to? is hoary Ruby.

I've never used Justin's haXe, so I'm completely unqualified to talk about how it solves these problems. I can only take his word for it. However....

There's no boilerplate if you predeclare a type!

Justin's followup argues that if you predeclare a type which defines an interface and use that interface pervasively throughout your system where you would normally use a type, you don't have to write boilerplate reflection code.

I agree, but I've categorized that as writing an interface (see Perl Roles versus Interfaces and ABCs): the act of explicitly declaring some combination of behavior and naming it and modifying your program to use that type instead of concrete, instantiable types.

You can do the same thing with roles. (You can also do more with roles.)

This is a fine technique, and I've used it to good effect. I don't classify it as duck typing, because it's an act of will to define this entity. I don't want to quibble over definitions, however.

Runtime reflection checks are rare in my code

Good! You don't run into the same problems I do.

Roles are rigid and top down

I'm not sure I agree with this. Most of the uses I've seen of roles are anything but rigid and top down. They're definitely more formal and structured than duck typing, but less so than subclassing inheritance. My colleague and sometimes co-author Ovid had a throwaway paragraph buried in his Flying Without Source Control rumination that using roles at the BBC has improved the flexibility of their code such that projects that would have taken multiple months now take minutes.

Roles violate Perl's loosely structured nature

"Violate" is a strong word, but a robust system is a robust system. Does Perl's test-infected culture violate its nature? Does the presence of an optional mechanism for managing code reuse and declaring the expected behaviors and relationships of entities without mandating their implementations of their behavior substantially change the nature of the language for the worse?

That's a philosophical question, and I'm not going to answer the worst case scenario. I prefer to believe that allowing the use of roles -- without mandating their use -- provides Perl with more opportunities to write great, robust, extensible, and understandable code. Certainly Ovid's writings demonstrate that his team has used them to great effect. (While he's one of the best and most reliable coders I've ever worked with, he'll be the first to disclaim any notions of rockstardom.)

I still don't get it

That's fine. Roles are subtle. I spent several years burning my fingers trying to combine code reuse with sufficient genericity and robust coding. When we saw Andrew Black present the Smalltalk Traits paper, a couple of my colleagues convinced me that what I was developing was sufficiently similar to the paper that Perl 6 could borrow their formalisms and achieve my goals.

If you really want to stretch your mind, consider this: what if your language had pervasive multi-dispatch built in, and dispatched not based on the class of all of its dispatchable invocants, but on the role of all of its dispatchable invocants? Remember, every class implies a role.

I don't intend to disparage any code that uses duck typing (or inheritance or interfaces or abstract base classes) successfully... but consider the implications in the previous paragraph. How would you even build such a thing using those techniques? Remember my design constraints for roles:

  • They must not dictate the implementation of conforming entities, allowing inheritance or delegation or composition or reimplementation.
  • They must not require editing existing entities to enable or improve polymorphic capabilities.
  • They must be full-fledged members of the type system.
  • They must provide compile-time disambiguation and refuse ambiguous composition.

If those aren't your design goals, that's fine. You can write a lot of useful, maintainable programs without them. Yet I believe we can write even more programs with them.

2 Comments

I'd like to suggest to describe the usage of Roles in combination with method modifiers (before/after, override, etc). I think the real power of Roles opens only in this use case.

With Roles & method modifiers, its possible to accurately and consistently change the behavior of whole class hierarchy.

This is related to the fact, that the Role cannot override method, if its already defined in the class, but it can contribute a modifier to it.

I'm not sure though, whether method modifiers are present in perl6.

This is a great summary of all the criticisms... nice! I feel that you've done a good job of summarizing criticisms of reflection duck-typing in Perl. Maybe someday when you're not tied up with Perl, you can check out the way that haXe handles typing: http://haxe.org/ref/type_advanced . It's not really that exotic, but it becomes powerful since you can use the haXe compiler to target javascript, SWF, PHP, Neko, and even C++ targets, and still use all the nice Typing facilities of the haXe compiler.

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on May 8, 2009 10:29 AM.

Perl Roles versus Interfaces and ABCs was the previous entry in this blog.

Writing Perl 5's Support Policy is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?