October 31, 2017

...Learn TDD with Codemanship

Safer Duck Typing: Could We Have Our Cake & Eat It?

I've been thinking a lot lately about how we might reconcile the flexibility of duck typing, like in Python and Ruby, with the safety of static type checking like we have in Java and C#.

For many, the swappable-by-default you get with duck typing is a boon for an evolving domain model. And for me... well, there's a reason why people invented static type checking, and I would prefer not to go back to the bad old days of having to keep that model in my head.

Is there a "having our cake and eating it" arrangement possible here? I think there just might be.

In programming, "type safety" means there's a low risk of trying to invoke methods that the target object doesn't support. Static type systems like in Java enforce this as part of the design. All objects have a type, and that type describes what methods they support. Part of the "drag" of programming in Java is having to qualify this for every single object we want to use. Want to pass an object as a parameter in a method call? You'll need to specify what type that object is. So it's not possible to invoke a method that an object doesn't support - well, not easily, anyway.

It seems to me that, in languages like Python, we have that information in our code. We just need to apply it somehow.

In this example, two methods are invoked on a parameter of the calculate_net_salary() function. Whatever is passed in as that parameter value must implement those two methods with the exact signatures. But the Python compiler doesn't see that. If I passed in an instance of a class that didn't implement those methods, it would be perfectly happy.

Scala introduced a concept called structural typing, where we can specify with function parameters what methods any object passed in as that parameter value must support. e.g.,

def quacker(duck: {def quack(value: String): String}) {
println (duck.quack("Quack"))

I like how the required interface is defined by the client - I think this is how it ought to be. But I hate that I need to define this at all. It's duplicated information, and extra work that we have to remember to do. It, too, requires me to carry the model in my head. And when these contracts change, it's more code we have to change.

As far as I can see, the information's already in the function's implementation. We could examine the body of a function or method and build a table of the methods it calls. Then we trace back the source of the target instance. (This may require a bit of fancy shooting, but it's doable, surely?)

In my calculate_net_salary() I can trace the target of the invocations of income_tax() and national_insurance() to the parameter tax_calc. Following that back up the call stack to code that invokes calculate_net_salary(), I see that the class of the target instance is TaxCalculator.

Now I can examine this class to see if it does indeed support those methods.

This is something I've been saying all along about duck typing: the type is there, it's just implied.

I think what's needed is a belt-and-braces prototype to see if this could work in practice. If it really could work, then I - personally - would find something like this very useful. Would you? Do you know of something that already does this that my Googling has overlooked?

Posted 2 years, 9 months ago on October 31, 2017