Is it a fair assumption to say that an overloaded constructor for a class can be thought of as a ‘duck’ for duck typing? Let’s say that ClassA has an overloaded constructor, it would stand to reason that the different constructors are there for a reason — the object is being used in a manner that indicates all of its properties do not have to be set for the object to perform correctly.
So, I ask, is an overloaded constructor a duck?
No, It Isn’t Duck Typing
I would argue that this is not an acceptable form of duck typing in a static language.
In a language that supports duck typing, one would expect to be able to define a function that accepts an object foo
of unspecified type that defines a function (or property) bar
.
def call_bar (foo) {
foo.bar
}
The function doesn’t care what type foo
is, just that is defines bar
. Your system doesn’t provide any way for a function to take an object of any type, so long as that type defines the required functions.
So how could we get duck typing in a statically typed language?
1. Interfaces
Well, the typical approach in most languages is to define interfaces. This doesn’t get us all the way there, but it gets us close. We can at least define a contract by which we identify any pertinent functions/properties/etc on a type.
A function can then be written so that it accepts inputs by interface, rather than concrete classes. We retain the ability to check the type at compile time, to ensure that the required functions exist on the inputs, while getting the flexibility to ignore the implementation details.
2. Structural Typing
Scala has an interesting approach to this, which offers a much more flexible way of getting duck-typed behavior in a static language. Scala allows you to reference a type in terms of its members, using the following syntax:
def callBar(foo : { def bar() : Unit }) = {
foo.bar()
}
This will match any types that define the bar
function, accepting no inputs and returning void output. You also retain the static typechecking benefits of a static language, as opposed to relying on late-binding to determine whether or not inputs will have the right members.
4
No it isn’t.
Objects of a particular class all support exactly the same set of messages no matter what constructor they were created with. Overloading constructors merely means that it isn’t necessary to initialize explicitly all the fields that make up an object’s state. For instance, an object might have color with a sensible default value, so you can have a constructor with a color value and one without such a value, while a field that is an obligatory foreign key to another entity can’t be made up by the class, so it always has to be a constructor argument. But in Java, all class members are fully defined when an object is constructed (in fact, at run-time there is no such thing as an undefined value), so comparing Java objects to those in duck-typed languages isn’t helpful.
It’s fair to say that overloaded constructors specify different ways to construct a duck. That’s not duck typing, though.
Consider the following class:
public class Rectangle
{
public Rectangle(Point A, Point B)
{
// Constructs a rectangle by using points A and B as a diagonal,
// and filling in the rectangle with the usual lines at right angles.
}
public Rectangle(Point A, Length diagonal, Angle angle)
{
// Computes Point B using the specified size and diagonal, and then
// calls the first constructor.
}
public Rectangle(Point A, Point B, Point C, Point D)
{
// Constructs a rectangle from the four supplied points, and
// verifies that all angles are 90 degrees.
}
}