This question has been asked here, but received poor answers and didn’t clarify the issue. I believe it justifies asking it again.
I understand that you can have duck typing with either dynamically typed languages or with statically typed ones (but examples for these are rare, such as C++’s templates).
However I’m not sure if there is such a thing as a dynamically typed language without duck typing.
Duck typing means that the type of an object is based on the operations and attributes it has at a given point in time. Is there a way to do dynamic typing without inevitably supporting duck typing?
Let’s look at this Python code for exampe:
def func(some_object)
some_object.doSomething()
something = Toaster()
func(something)
In dynamically typed languages, the type of an object is only known at run time. So when you try to do an operation on it (e.g. some_object.doSomething()
), the runtime has only one choice – which is to check whether or not the type of some_object
supports doSomething()
, which is exactly what duck typing is.
So is it possible to have a dynamically typed languages without duck typing? Please explain.
3
First, to make sure we’re talking about the same things, I would start with some definitions.
Static typing means that type errors are reported at compile time, while dynamic typing means that type errors are reported at runtime.
Duck typing means that a piece of code requires that an object supports the operations that are used and nothing more.
Structural typing requires that an object supports a given set of operations (even if some of them may not be used).
Nominal typing requires that object is exactly of the given type, or is a subtype of that type.
So, as you can see, structural typing is stricter than duck typing and nominal typing is stricter than structural.
Now, I’m going to talk about the TypeScript language, because it nicely illustrates most of these options.
Consider the following TypeScript program:
interface Person {
Name : string;
Age : number;
}
function greet(person : Person) {
alert("Hello, " + person.Name);
}
greet({ Name: "svick" });
Since the object that’s passed to greet
doesn’t have the Age
property, this causes a compile time error, demonstrating that TypeScript uses static structural typing.
Despite the error, the code above actually compiles to the following JavaScript, which runs fine:
function greet(person) {
alert("Hello, " + person.Name);
}
greet({ Name: "svick" });
This shows that TypeScript also uses dynamic duck typing.
If the code instead compiled to something like:
function greet(person) {
if (!(typeof(person.Name) == 'string' && typeof(person.Age) == 'number'))
throw 'TypeError';
alert("Hello, " + person.Name);
}
Then that would be an example of dynamic structural typing, because it checks that the object has the required properties of the required types, even if the function itself doesn’t require them.
If it compiled to:
function greet(person) {
if (!(person instanceof Person))
throw 'TypeError'
alert("Hello, " + person.Name);
}
That would be an example of dynamic nominal typing, because it checks the name of the type of the object, not its structure.
What this all shows is that dynamic non-duck typing is possible (both structural and nominal). But this approach isn’t used very often, because it mostly combines the disadvantages of non-duck typing (you have to specify types explicitly; less flexible) and dynamic typing (type errors only show at runtime and only in the code that actually runs).
If you’re going to add type annotations to make non-duck typing possible, you might as well check the types at compile time.
9
Apparently (from what I read) Duck typing has meaning only in an
object oriented context, when functions are attached as methods to
objects. Then when you write duck.quacks(3)
, this will work if the
current value of duck
has the method quacks
.
Dynamic typing is not necessarily attached to an OO view with methods.
You can define the type real
with associated operator or function
+: real*real->real
, and the type rational
with the associated
operator +: rational*rational->rational
. Then if you write a+b
, in
a dynamically checked time system, both your variables a
and b
may
have a +
operator, but you get a run-time type error.
Dynamic typing checks for categorial consistency of values, possibly
several of them.
Duck typing checks for behavioral consistency of the code with an
object at hand (a single one, as far as I understand it).
In a sense duck typing is a form of runtime polymorphism, except for
the fact that it applies only to access methods of a single object.
However, one could possibly define a more general form of run-time
polymorphism, where the operator +
to be executed would be
determined on the basis of all arguments. So that a duck and a chicken
could danse together if they share a common danse function. They could
have several so that ducks can danse also with geese with a different
function. But that seems a bit complicated. As far as I remember,
something of the kind (with probably more structure) may have been
possible with the generic functions of the language EL1, a very old
forerunner of object oriented languages.
2
An answer by analogy:
Can you buy a convertible and never put the top down? Sure. It’s probably not the best way to spend your resources, since you’re paid extra for some features (e.g. convertible top, extra structural stiffening due to lack of roof as structural element) and gotten some worse results (e.g. extra road noise, possibly lower crash safety, smaller storage compartments) as a result of investing in that feature you won’t use. But it’s technically feasible.
It’s the same with dynamic languages and duck typing. You’ve given up the higher efficiency and the compile-time type safety assurances of static languages. For what? Generally for the simplicity of duck typing. Variables and collections can hold anything, and you don’t need to do a lot of up-front specifying just what. Mixed collections like [ 12, "gumbo", 12.4, 4+4j ]
(an integer, a string, a floating point value, and a complex value) are trivial, and you don’t have the constant type-casting you see in Java code (for example).
It’s possible in a dynamic language like Python to create objects that are not duck-typed:
class Hungarian(object):
self __init__(self):
self.value = []
self addInt(self, intValue):
self.value.append(intValue)
self addFloat(self, floatValue):
self.value.append(floatValue)
self addComplex(self, complexValue):
self.value.append(complexValue)
# ...
But as you might notice, there’s no real checking of the types, and each of the methods is implemented with a duck-typed built-in structure (list
). Having paid the price for dynamism, you might as well put the top down and get the resulting simplicity:
class NotHungarian(object):
def __init__(self):
self.value = []
def add(self, whatever):
self.value.append(whatever)
4