Question

I understand what dynamic and static type systems are, and what duck typing is. But I don't understand how you can have a static language that supports duck typing. To my understanding only a dynamically typed language can support duck typing.

This answer from StackOverflow explains that "Duck typing is something that is completely orthogonal to static, dynamic, weak, or strong typing." It gives an example from C++ for duck typing in a statically typed language, but I'm not a C++ programmer and I don't understand it.

I'd like an explanation of how it's possible for a static language to support duck typing.

Was it helpful?

Solution

In my experience, it is simply a language that uses static typing with a Structural Type System. It essentially applies the "walks like a duck, talks like a duck" check at compile type so that the programmer doesn't need to provide annotations to specifically sub-type things.

This has a very large benefit when you're trying to glue two (or more) libraries together. With a nominative type system, if you had some interface in one library and an object in the other, they don't know about one another and can't sub-type - even if the object satisfies the interface's requirements. Structural typing makes that okay.

Making the language static just means that the compiler does that check at compile time, giving you an error early (and clearly) when that object doesn't really satisfy the interface.

There are a few esoteric languages that do this, but most structurally typed languages are also dynamically typed and almost all nominative typed languages are also statically typed.

OTHER TIPS

The usual meaning of such a term is just structural typing.

In structurally typed systems, things have a static type. However that type is based on the actual structure of the type rather than any particular type name.

For example, let's say we have the Python code

def foo(bar):
  bar.baz()
  bar.quux(5)

Now it's not clear what the type of bar is, but whatever it is we know

  • It has a method baz which takes no arguments
  • It has a method quux which takes 1 integer argument

Now in a structural type system we could assign foo a type

foo : forall a b. r{baz : () -> a, quux : (int) -> b} -> Void

where that funny r thing means

Any type r which the methods ...

Many languages implement some subset of structurally typed features, C++ for example implements "structural typing" via templates. However this is a slightly adhoc approach.

Other languages implement row-types. These are just structurally typed records/structs! Types where we can say something like "we want a record with at least the fields ...". I believe purescript implements these.

Go has something like structural types with it's "implicit interfaces". These are just interfaces that a type implements automagically. However this isn't full structural types since it doesn't allow for a structural type to be handled parametrically, that is there's no way to say something like

foo :: r{a : int} -> w{a : int} -> r
foo r w = w -- Type error!!

Since everything is "upcasted" to an interface, rather than merely treated opaquely.

There's been some talk of adding these to Haskell view -XOverloadedRecordFields, but I'm not aware of any real progress on structural typing in it's full generality.

Statically typed just means the types are checked at compile time. It's just as easy for a compiler to check that a type has a method with a certain name and signature as it is to check that a type is a part of a specific inheritance hierarchy. The trick is finding a concise way to specify "this argument to this function is any type that has a method with this specific name."

The method I'm most familiar with to accomplish this is using a type class, which is a declaration that basically says, "any type that implements all these functions can be referred to using this name."

Usually you must specifically declare a type to be an instance of a type class, but it doesn't have to be in the same code that the type itself is defined, which means anyone can add their own type class after the fact to types they don't control. That's sort of a compromise to total duck typing, more like, "if it walks like a duck and quacks like a duck, then anyone anywhere can explicitly declare it a duck-like thing, and everyone else can treat it like a duck."

However, it's not that big of a stretch to allow types to be implicitly added to a type class, you just lose a bit of control. As Amon and Jozefg pointed out, the go language has interfaces which basically act like implicitly added type classes.

Do you know Typescript?

It may seem as a simplistic example but here it goes: it's a language that compiles to Javascript. Now, Javascript itself is dynamically and duck typed, but let's just forget about JS for a second.

Typescript supports static typing, meaning that it will check if your types are correct during compilation and spit out warnings if it thinks you may have made an error. (You can follow up the tutorial and it will show you an example of this)

However, even if it supports static typing, it also has the whole JS duck typing thing. You can use var all around the place, omitting the types Typescript adds and the code will compile and run.

So, it's statically typed, but you can go quack-quack, too. It will even check the quack-quack for you if you let it.

Duck typing is for your typing convenience and in some languages for having generics (such as C++). Static typing is for checking the types you have specified during compilation, if you're working on a compiled language.

It doesn't have to be. You can implement 95%ish of duck typing in a static language without that much work. The last 5% you need dependent typing for and it's much more complicated, but that's still a statically typed system. Specifically, the remaining 5% is for conditionally using methods depending on other values, and so having code that still works on data that does not support all of the methods you code conditionally calls.

Here's a chunk of Haskell that I was tinkering with and it implements something very much like static duck typing:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, 
UndecidableInstances, IncoherentInstances, FlexibleContexts #-}

data Addon a b = Addon a b deriving Show

class Plugin a b where
    first :: b -> a
    set   :: a -> b -> b

instance Plugin a a where
    first = id
    set   = const

instance Plugin a (Addon a b) where
    first (Addon a _) = a
    set x (Addon a b) = Addon x b

instance (Plugin a b) => Plugin a (Addon c b)where
    first (Addon _ b) = first b
    set x (Addon a b) = Addon a $ set x b

class Runnable e b c where
    run :: e -> b -> c

instance Plugin a b => Runnable (a -> c) b c where
    run f p = f (first p)

instance (Plugin d b, Runnable e b c) => Runnable (d -> e) b c where
    run f p = run (f $ first p) p

-- The constructor function should NOT be polymorphic *at all*
apply constructor f x = set (constructor $ run f x) x

The Plugin typeclass exists to express containment of types within other types. A type which is an instance of Plugin a b means that it is a type b for which first b can be a value of type a. The two instances given are all that is needed to derive all relevant cases and no further instance declarations are needed.

The Runnable typeclass allows us to chain Plugin instances together and so call a curried multi-argument function on a single datatype so long as it contains a valid component type for each curried argument of the function. Again, no further instance declarations are needed for usage.

The apply function allows us to take a composite type, apply a function to it, and store the result inside that composite type (so long as it already contains a value of the type we wish to store)

Usage would look like so:

data Increment = Inc Integer
data Counter   = Count Integer

counter1 = union (Inc 2) (Count 3)
counter2 = union (Inc 1) (Count 0)

increment (Inc i) (Count c) = i + c

And indeed, this compiles, and we can run it and observe the output:

*Main> apply Count increment counter1
Addon (Inc 2) (Count 5)
*Main> apply Count increment counter2
Addon (Inc 1) (Count 1)

Granted, we had to turn on some rather ugly type extensions, but those extensions are not required to use the library, only to write it. The constructor argument to apply cannot be polymorphic because that would overload the type inference system and it would be unable to make the necessary intermediate type derivations. I was trying to get around that, but I have not made any progress in that direction.

"It gives an example from C++ for duck typing in a statically typed language, but I'm not a C++ programmer and I don't understand it."

C++ support compile time duck typing aka templates. Templates is basically super powered generics. Eg:-

template <class NUM> NUM add(NUM one , NUM two)
{
  return one + two;
}

This function accepts two parameters of template type NUM(Which I just named, there is no meaning you can use anything as the type name). At compile time the compiler checks to see how the function is used.

eg:-

 add(2.0,2.0)

 add(1,2.0)

If the compiler finds there is no error with the types used(double,int), then it will create a function for that type substituting for the template type.

eg:-

double add(double one, double two)
int add(int one, int two)

This is called compile time duck typing. Because of this , you don't have to worry about times if they are compatible.

If the code shown below compiled in a language that otherwise resembled C, then this fictitious language would presumably exhibit "static" duck typing (according to Wikipedia, at least):

typedef struct s1{
 int a;
 int b;
};

typedef struct s2{
 int a;
 int b;
};

s1 thing1;
s1.a=1;
s1.b=2;

s2 thing2;
thing2=thing1; //This is where a "real" C compiler would complain
Licensed under: CC-BY-SA with attribution
scroll top