7

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.

8
  • 3
    A Google search for "static duck typing" goes directly to Are there any static duck-typed languages?, which may provide sufficient answers and links to satisfy your curiosity. Commented Aug 11, 2014 at 20:25
  • 4
    Do you understand C++ iterators? That is a perfect example. There is no common Iterator base class. Any object which supports operator++ and operator!= (loop increment and bounds check) can function as an iterator, because it walks and quacks like an iterator. This could be one of the many iterator classes defined by the STL, or even a bare pointer.
    – user22815
    Commented Aug 11, 2014 at 21:03
  • 3
    I'd give an example in Standard ML but then I'd have to teach you Standard ML.
    – Doval
    Commented Aug 11, 2014 at 22:34
  • 2
    @MooingDuck you are right, there is more than what I mentioned, but I had the right idea. Java says "if you implement java.util.Iterator you are an iterator." C++ says "if you waddle and quack like an iterator, you are one." Simple and good example of the two types of typing.
    – user22815
    Commented Aug 11, 2014 at 23:51
  • 1
    @LieRyan I never equated functions and iterators. I think you are reading too far into what I am saying. My point is that C++ allows duck typing. Of course, template functions allow it too, but I was trying to come up with a very simple example.
    – user22815
    Commented Aug 12, 2014 at 0:23

7 Answers 7

13

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.

4
  • 2
    Golang is an interesting example of a not-too-esoteric language that works with static duck typing. Here is an example showcasing static duck typing. Yes, “structural typing” is a better term.
    – amon
    Commented Aug 11, 2014 at 20:55
  • @amon - ah, you're right. I had forgotten since I've not spent enough time with Go (and Swift, and some other recent ones).
    – Telastyn
    Commented Aug 11, 2014 at 20:57
  • Two different ways a compiler statically checks Structural Typing, are Row Based Polymorphism and Structural Subtyping. Commented Jun 5, 2019 at 10:05
  • for example something called Typescript
    – mindlid
    Commented Mar 16, 2023 at 5:39
5

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.

3
  • I'm not sure whether C++ templates can be called a structural type system (I think I've never seen it referred to as such). There's not really any type system that could be structural, it just substitutes the types and sees if that monomorphized code is valid.
    – user7043
    Commented Aug 11, 2014 at 20:35
  • 1
    @delnan This is what I meant when I said "adhoc". You can formalize you can view C++ as having inferred and unutterable structural types. Commented Aug 11, 2014 at 20:37
  • @delnan look at the "concepts" proposal for future c++. The standards committee is wanting to formalize structural typing in templates further.
    – Caleth
    Commented Nov 27, 2016 at 10:07
4

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.

4
  • To be clear: Type classes and similar ad-hoc polymorphism usually aren't considered duck typing though, since one must explicitly opt into the type class (even if doing so can happen anywhere in the program) rather than magically having it work after defining just the method.
    – user7043
    Commented Aug 11, 2014 at 22:01
  • Yes I agree. It is the closest you usually get in a statically typed language though, and the OP was wondering how that would work. Commented Aug 12, 2014 at 12:32
  • @delnan I guess if you really want to be strict about the definitions, they're not the same thing, but I think they're close enough in practice - the key thing to me is that you can apply type classes to pre-existing types at any point.
    – Doval
    Commented Aug 12, 2014 at 14:59
  • @Doval That is certainly part of the appeal, but the Pythonista in me and some people I've talked to fights toe and nail against having to write as little boilerplate as instance Blah MyType blahFoo = myFoo.
    – user7043
    Commented Aug 12, 2014 at 15:35
2

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.

4
  • Duck typing/structural typing isn't the same as type inference.
    – Doval
    Commented Aug 11, 2014 at 21:47
  • I searched for the difference and you're correct. So, in this case, would Typescript duck typing be the object system? Commented Aug 11, 2014 at 21:52
  • Like, if I had polymorphism? Commented Aug 11, 2014 at 21:52
  • 1
    It's only duck typing if you can substitute any two classes as long as they contain the same methods and types.
    – Doval
    Commented Aug 11, 2014 at 22:00
0

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.

-1

"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.

-2

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
2
  • 2
    Programmers is about conceptual questions and answers are expected to explain things. Throwing code dumps instead of explanation is like copying code from IDE to whiteboard: it may look familiar and even sometimes be understandable, but it feels weird... just weird. Whiteboard doesn't have compiler
    – gnat
    Commented Aug 12, 2014 at 7:00
  • I posted a response here and it was deleted. To summarize, I said that I will respect the site guideline, but that I don't agree with it. Commented Aug 12, 2014 at 15:24

Not the answer you're looking for? Browse other questions tagged or ask your own question.