Ok, so there is what might be my first-ever "link bait" title. Especially dangerous because I am about to jump into a discussion propagated by some well-known, highly-respected developers against whom my own self-taught, inexperienced knowledge pales.
I lack the formal schooling or training to join this discussion at the academic level, I simply know what I think I know, and will attempt to toss my opinion out there and see what happens. Actually, that is another first for me . . . I normally recognize that I don't have sufficient "real-world" experience or education to express a strong opinion on such things. Must be early Saturday morning, and I might need more coffee.
Image by Federico Reiven | Some Rights Reserved
UPDATE: Glen Block added his own, very well written thoughts on Duck Typing at his blog at Code Better.Com. Great read.
Eric Lippert recently posted a typically well-stated article on his blog, Fabulous Adventures in Coding (if you don't already subscribe, now would be a perfect time to do so. His stuff really is THAT GOOD), setting out his thoughts relating Duck Typing to what we in the statically-typed world might recognize as Late Binding. When I woke this morning, I found in my inbox from Phil Haack's blog feed a follow-up in which aptly uses the square filters/round holes issue from the ill-fated Apollo 13 mission as an analogy for Duck Typing.
UPDATE 2: Phil Haack posted a follow-up to his own article on 1/6/2014 on his blog, You've Been Haacked.
Both Lippert and Phil Haack appear to be attempting to reconcile the concept of Duck Typing with static type systems, and statically typed programming practices.
Mr. Haack appropriately pulls out some python code, but in my humble opinion only brings the Duck Typing concept partway home, by looking at method overriding in dynamically-typed languages as an example of how Duck Typing might be effectively demonstrated. Most importantly, Phil Haack hits on what, to me, is the deciding gem of the quest to understand:
"I think some of the confusion lies in the fact that duck typing isn't so much a type system as it is a fuzzy approach to treating objects as if they are certain types (or close enough) based on their behavior rather than their declared type. This is a subtle distinction to late binding."
-Phil Haack
Unfortunately (for me, anyway) he then goes on to discuss Duck Typing from a perspective of method overloading. Obviously, Phil Haack knows of which he speaks, and I in no way disagree with what he is saying. However, for myself, the important way to look at Duck Typing is unnecessarily obscured from this perspective.
My question is, are we trying too hard to pigeonhole a simpler concept?
As one who has spent most of my time coding in a statically-typed language, I have developed some very basic coding practices and ways of thinking which make the transition to a dynamic approach most challenging. The mushy, malleability of dynamic languages and typing creates a situation which forces us to re-examine what we think we know about type systems, method signatures, and such (at least, if we come from a statically-typed background).
I have spent a lot of time trying to reconcile the dynamic mushiness with my static-typed tendency to create code which, through the action of compile-time type checking, forces the client of my code to "do the right thing."
If we examine some simple, but common design strategies in C# code, we can understand this tendency.
When working in C#, Java, or other statically-typed language paradigm, we become very accustomed to working within the type system, and not only allow it to dictate much of our program structure, but also to assist us in protecting clients of our program from doing the wrong thing.
void TheAnimalSpeaks(Animal animal)
{
Console.WriteLine(animal.WhatISay);
}
Before the code above will even compile, there are three things, at a minimum, which must be satisfied:
- There must be an
Animal
class defined, and available to our code
- The argument passed in to the method
TheAnimalSpeaks
must be of class Animal
- The
Animal
class must itself define a WhatISay
Property which resolves to a string
Therefore, we would need, at a minimum, a class Animal
, which includes a property WhatISay
:
The Animal Class:
class Animal
{
public string WhatISay { get { return "I Said Something"; } }
}
In the C# world, we now know that any client code which wished to call the method TheAnimalSpeaks
will be providing an instance of Animal
as an argument, and that instance will indeed make available a property named WhatISay
. We know this because the client code will not even compile until it does these things.
Also in C#, if we wanted to define different sorts of animals which all share some common behaviors and properties, we could define an IAnimal
Interface, which would need to include the property WhatISay
, and then modify our method TheAnimalSpeaks
to accept an instance of IAnimal
as an argument:
The IAnimal Interface in C#:
interface IAnimal
{
string WhatISay { get; }
}
We would then Implement the IAnimal
interface on any class we wished to exhibit the property WhatISay
:
Classes Implementing the lAnimal Interface in C#:
class Duck : IAnimal
{
public string WhatISay { get { return "Quack"; } }
}
class Wolf : IAnimal
{
public string WhatISay { get { return "Howl"; } }
}
class Toaster : IAnimal
{
public string WhatISay { get { return "Hi, I'm not even an animal!"; } }
}
Now, we can pass any instance of IAnimal
to our method TheAnimalSpeaks
and it will work. The compiler will make certain for us that, before we can even run our program, any code which calls this method is passing an argument which implements IAnimal
.
static void Main(string[] args)
{
IAnimal theBigBadWolf = new Wolf();
TheAnimalSpeaks(theBigBadWolf);
var theFleeingDuck = new Duck();
TheAnimalSpeaks(theFleeingDuck);
}
static void TheAnimalSpeaks(IAnimal animal)
{
Console.WriteLine(animal.WhatISay);
}
In the code above, we can either explicitly declare an instance of type IAnimal
, or we can simply instantiate a type which implements the IAnimal
interface, and our code will compile and run. We can also see that, so long as a class implements the IAnimal
interface, it matters not what the actual class represents (as we can see from the Toaster
class, which presumable has little or nothing to do with animals, but can still be passed to the method TheAnimalSpeaks
, even though it is not necessarily a good example of domain modelling!).
As a developer with a lot of background in a statically-typed environment, I found the transition to the dynamic world of JavaScript most challenging. In statically-typed land, we learn to expect that our method signatures, and compiler type checking will protect consumers of our code from passing in the wrong arguments.
In other words, the compiler enforces the contracts established by the method signatures we devise, and we carefully create our domain models to work within these boundaries. We devise base classes and interfaces to define type expectations at program boundaries, and sleep well at night knowing that client code can't even call into our methods until certain minimum conditions are met.
When we move into a dynamically-typed environment, all that goes out the window.
Dynamically-typed languages do not perform any compile-time type-checking, instead relying on a run-time type system as described by Eric Lippert so effectively. All of the Type definition and interface business we discussed previously has little meaning in such an environment, because none of the advantages afforded by interfaces and the contracts they provide for the compiler are realized.
In my under-educated understanding, it seems to me common dynamic languages utilize Duck Typing implicitly. If seems to me we go to great lengths to describe a concept which should be fairly simple at its core:
From the Wikipedia definition referred to by Lippert:
"duck typing
is a style of typing in which an object's methods and properties determine the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface."
More Explicitly:
"In duck typing, one is concerned with just those aspects of an object that are used, rather than with the type of the object itself. For example, in a non-duck-typed language, one can create a function that takes an object of type Duck and calls that object's walk and quack methods.
In a duck-typed language, the equivalent function would take an object of any type and call that object's walk and quack methods. If the object does not have the methods that are called then the function signals a run-time error. If the object does have the methods, then they are executed no matter the type of the object, evoking the quotation and hence the name of this form of typing."
I have little background in compiler design, and I accept as axiomatic that under the covers, behind the scenes, there is a whole lot going on under the hood in the implementation of both the C# compiler, and the JavaScript interpreter to make both type systems work properly.
However, to understand Duck Typing at the useful level of abstraction required to write code, the above definition can be illustrated by contrasting the previous C# examples with some simple JavaScript:
Let's consider some similar code in JavaScript. Note that JS does not have any notion of "classes." I will, however, model up some similar constructs using the JavaScript Module pattern (I am using the Node.js runtime here, such that I can execute the code in the terminal).
First, let's look at a Duck module:
A Duck "Class" (Module) in JavaScript:
var Duck = function() {
this.whatISay = "Quack";
};
module.exports = Duck;
Here, we have defined a module which returns a "duck" object. We can do the same for our Wolf
"class":
A Wolf "Class" (Module) in Javascript:
var Wolf = function() {
this.whatISay = "Howl";
};
module.exports = Wolf;
Now, let's write a method analogous to our C# method TheAnimalSpeaks
, and execute it in the terminal using Node.js:
Running the Javascript Equivelent Code:
var Duck = require("./Duck");
var Wolf = require("./Wolf");
this.theAnimalSpeaks = function(animal) {
console.log(animal.whatISay);
};
var myDuck = new Duck();
this.theAnimalSpeaks(myDuck);
var myWolf = new Wolf();
this.theAnimalSpeaks(myWolf);
When we run the code above, the output in the terminal is, predictably:
Terminal Output from theAnimalSpeaks method in JavaScript:
Quack
Howl
As we can see, the JavaScript method theAnimalSpeaks
cares not one whit what kind of object we pass in to it. In fact, it doesn't really recognize the argument's type until runtime, and even then, only recognizes that it is to look for a property on the passed in argument named whatISay
. If it finds such a property, it will attempt to use it. If not, it will return the value undefined
. If, instead of a property we were attempting to access a method, the JS runtime would throw a runtime error indicating that no such function exists.
Let's make a JavaScript analog of our Toaster
class, but let's give it some sort of "toaster-like" method, and not include the whatISay
property:
The JavaScript Toaster "Class" (Module):
var Toaster = function() {
this.startToast = function(minutes) {
console.log("the toast will cook for " + minutes + " minutes");
};
};
module.exports = Toaster;
Here, we have created a "class" with a single method - the startToast()
function which accepts an argument specifying how long to cook the toast. If we modify our application code to call into the Toaster
module, attempting to both access the whatISay
property as well as call the startToast()
method, we see in the terminal output that the attempt to access the non-existent property returns undefined
, and the call to startToast()
executes as expected.
ApplicationCode Consuming the Toaster "Class" (Module):
var Duck = require("./Duck");
var Wolf = require("./Wolf");
var Toaster = require("./Toaster");
this.theAnimalSpeaks = function(animal) {
console.log(animal.whatISay);
animal.startToast(4);
};
var myToaster = new Toaster();
this.theAnimalSpeaks(myToaster);
The terminal output from the above is:
Terminal Output from Application Code Consuming the Toaster "Class" (Module):
undefined
the toast will cook for 4 minutes
Obviously, the above is NOT an example of effective Duck Typing, as we are mixing metaphors and domain objects here. In similar fashion, let's try to pass one of our animal
objects into the code as it currently exists:
Passing a Duck object into the Modified Application Code:
var Duck = require("./Duck");
var Wolf = require("./Wolf");
var Toaster = require("./Toaster");
this.theAnimalSpeaks = function(animal) {
console.log(animal.whatISay);
animal.startToast(4);
};
var myDuck = new Duck();
this.theAnimalSpeaks(myDuck);
The code above throws an error informing us that the object has no method startToast()
.
The contrived examples above partially illustrate the power and danger in the Duck Typing concept. We have taken a method which accepts an argument, animal
, and attempts to access certain properties and/or methods that it expects to find. There is nothing in the method "signature" beyond the name of the argument to tell us what methods and properties are expected, and the compiler is not going to help us prior to runtime. But if that object presents the expected properties/methods called by the code, all is well.
This is in keeping with the Wikipedia definition:
"duck typing
is a style of typing in which an object's methods and properties determine the valid semantics . . ."
Let's look at one more silly example of how Duck Typing might be of use (or danger, as the case may be).
Returning to our application code, lets ditch the call to the startToast()
method, which we used previously only to illustrate how calling a non-existent function throws an error. Let's instead return to the idea that the method theAnimalSpeaks()
expects to receive an argument animal, and that it expects the animal to have a property named whatISay
which returns a string:
The Original Application Code:
var Duck = require("./Duck");
var Wolf = require("./Wolf");
var Toaster = require("./Toaster");
this.theAnimalSpeaks = function(animal) {
console.log(animal.whatISay);
};
var myDuck = new Duck();
this.theAnimalSpeaks(myDuck);
var myWolf = new Wolf();
this.theAnimalSpeaks(myWolf);
Now, let's take things one more step in the direction of the ridiculous. Let's modify our myToaster
object instance at runtime, adding a property whatISay
, and then passing it to the method TheAnimalSpeaks
:
Modified Application Code using a Duck Typed Approach:
var Duck = require("./Duck");
var Wolf = require("./Wolf");
var Toaster = require("./Toaster");
this.theAnimalSpeaks = function(animal) {
console.log(animal.whatISay);
};
var myDuck = new Duck();
this.theAnimalSpeaks(myDuck);
var myWolf = new Wolf();
this.theAnimalSpeaks(myWolf);
var myToaster = new Toaster();
myToaster.whatISay = "I'm not an animal, but I speak and make toast";
this.theAnimalSpeaks(myToaster);
Terminal output from the example above is:
Terminal Output from Modified Application Code:
Quack
Howl
I'm not an animal, but I speak and ost
Here we have, for better or worse, seen the idea of Duck Typing in action. We passed three different objects to our method theAnimalSpeaks
. In all three cases, the object looked and acted like what the code was expecting, and so the runtime was able to properly execute the method.
In the case of the toaster, we forced the object to "look and act" like an animal for the purposes of our method call.
As our Wikipedia definition says:
" [. . . a Duck Typed] function would take an object of any type and call that object's walk and quack methods . . . If the object does have the methods, then they are executed no matter the type of the object . . ."
To me, as I try to write better, more idiomatic JavaScript code (I have a long, long way to go here), I find for the moment that learning to "think duck typing" is more valuable than attempting to define it in terms I relate to static type systems.
I am not sure one can say that a Duck Typing represents a Typing System per se. One might instead subscribe to the notion that to varying degrees, certain dynamic languages afford us the ability to employ a Duck Typed approach.
One line in the Wikipedia Entry that is not given the attention I feel it deserves by mssr.'s Lippert or Haack is:
"Duck typing is aided by habitually not testing for the type of arguments in method and function bodies, relying on documentation, clear code and testing to ensure correct use."
This has been my experience when working with JavaScript so far. You want to call a method, you need to figure out what it expects as arguments, and make sure whatever you are passing in has the properties and/or methods expected by the method implementation. And THAT'S IT.
To my mind, JavaScript (as an example) allows us to design a method which accepts an argument of whateverTheHellIWant
, and the code will run properly so long as the argument implements the properties and behaviors expected by the method code.
Obviously to me, we want to adhere to some pretty solid design and naming conventions here. Additionally, there is probably no more clear case for effectively documenting an API such that consumers can more easily understand what is expected when calling your code.
Another way to approach the idea is to return to our concept of the interface in statically-typed systems. In these cases, and in our C# examples at the beginning of this article, we can see that interfaces in a statically-typed scenario allow us to say "This object looks and acts like a duck" even if it is not.
In a dynamic language, the ceremony associated with compile-time type checking, interface declaration, and such is unneeded. There is no compiler to enforce the type system rules, or to make sure our object implements a particular interface. This affords us an exceptional degree of freedom in designing our application code, and also places on us, the developer, the responsibility for ensuring our code will function correctly.
The preceding was inspired by both my own quest to better understand the languages I am using, and by reading two articles by developers I admire, and who know better than me. If anyone thinks I am trying to argue the points they make, please understand I am not. I am merely trying to synthesize what those two luminaries wrote with my own understanding, at the level that works best for me. If it helps anyone else, so much the better.
I am new to the world of Dynamic Languages in general, and JavaScript specifically. I am not a compiler builder, nor do I have any sort of academic training in language design.
CodeProjectJohn on Google