On the novelty factor of compile-time duck typing

Or structural type systems for the pendantic, but I think most people know what I mean when I say “compile-time duck typing”.

For one reason or another I’ve read quite a few blog posts about how great the Go programming language is recently. A common refrain is that Go’s interfaces are amazing because you don’t have to declare that a type has to satisfy an interface; it just does if its structure matches (hence structural typing). I’m not sold on how great this actually is – more on that later.

What I don’t understand is how this is presented as novel and never done before. I present to you a language from 1990:

template <typename T>
void fun(const T& animal) {
    cout << "It says: " << animal.say() << endl;
}

struct Dog {
    std::string say() const { return "woof"; }
};

struct Cat {
    std::string say() const { return "meow"; }
};

int main() {
    fun(Dog());
    fun(Cat());
}

Most people would recognise that as being C++. If you didn’t, well… it’s C++. I stayed away from post-C++11 on purpose (i.e. Dog{} instead of Dog()). Look ma, compile-time duck typing in the 1990s! Who’d’ve thunk it?

Is it nicer in Go? In my opinion, yes. Defining an interface and saying a function only takes objects that conform to that interface is a good thing, and a lot better than the situation in C++ (even with std::enable_if and std::void_t). But it’s easy enough to do that in D (template contraints), Haskell (typeclasses), and Rust (traits), to name the languages that do something similar that I’m more familiar with.

But in D and C++, there’s currently no way to state that your type satisfies what you need it to due to an algorithm function requiring it (such as having a member function called “say” in the silly example above) and get compiler errors telling you why it didn’t satisfy it (such as  mispelling “say” as “sey”). C++, at some point in the future, will get concepts exactly to alleviate this. In D, I wrote a library to do it. Traits and typeclasses are definitely better, but in my point of view it’s good to be able to state that a type does indeed “look like” what it needs to do to be used by certain functions. At least in D you can say static assert(isAnimal!MyType); – you just don’t know why that assertion fails when it does. I guess in C++17 one could do something similar using std::void_t. Is there an equivalent for Go? I hope a gopher enlightens me.

All in all I don’t get why this gets touted as something only Go has. It’s a similar story to “you can link statically”. I can do that in other languages as well. Even ones from the 90s.

Advertisements
Tagged , , ,

7 thoughts on “On the novelty factor of compile-time duck typing

  1. Chibisi says:

    I’m am not sure that I agree with your assertion:

    “But in D and C++, there’s currently no way to state that your type satisfies what you need it to due to an algorithm function requiring it…”

    Correct me if I’m wrong but this can be done pretty straight forwardly with template specializations and interfaces:

    “`
    import std.stdio: writeln;

    interface Animal{
    string say() const;
    }

    class Dog: Animal{
    string say() const{ return “woof!”;}
    }

    class Cat: Animal{
    string say() const{ return “meow!”;}
    }

    template fun(T: Animal)
    {
    void fun(T animal)
    {
    writeln(“It says: “, animal.say());
    }
    }

    void main() {
    fun(new Dog());
    fun(new Cat());
    }
    “`

    In this case anything that doesn’t subscribe to the interface gets turfed out. In addition correct me if I am wrong but is this not the basis of doing multiple dispatching in D?

    • atilaneves says:

      I meant using structs – in D using classes and interfaces has a cost in terms of how they’re used. I’d never thought of using them for compile-time dispatch though! Interesting.

    • John Hall says:

      You are explicitly inheriting from the interface. You could just as easily re-write the function as below and your example still compiles (and gives good error messages if you re-write Cat’s say function to sey).

      “`
      void fun(Animal animal)
      {
      writeln(“It says: “, animal.say());
      }
      “`
      I think what Atila is referring to is a case where the fun function can call Animal types without a class explicitly inheriting from Animal. For instance, suppose you re-write Cat and a version of fun as below. Now you get error messages that say that it can’t deduce the type properly.

      “`
      class Cat {
      string say() const
      {
      return “meow!”;
      }
      }
      void fun(T: Animal)(T animal)
      {
      writeln(“It says: “, animal.say());
      }

  2. Chibisi says:

    p.s. to provide that it is a compile time dispatch, you can pragma print …

    “`
    template fun(T: Animal)
    {
    void fun(T animal)
    {
    pragma(msg, T.stringof);
    writeln(“It says: “, animal.say());
    }
    }
    “`

    when the script compiles pragmas will show that Dog and Cat overloads of the fun functions are created.

  3. John Hall says:

    Have you had a chance to review: https://github.com/atilaneves/concepts/issues/2

    In addition, I think it would be helpful to have something like a subtypes.d that corresponds to implements.d. Implements can detect if a struct/class implements an interface or abstract class, but it doesn’t handle more general subtyping relationships. For instance, if a struct is a subtype of another struct, or if a struct subtypes something it alias this’es. While I have written a isSubTypeOf template (that I plan to submit to phobos), it doesn’t have the same capability as a concepts version.

    • atilaneves says:

      Sorry, somehow I missed your github issue. Taking a look now.

      structs don’t have subtypes in D. alias this doesn’t count.

      • John Hall says:

        In my reading of the D spec, there actually is very little discussion of subtypes. For instance, in the classes page it says “An AliasThis declaration names a member to subtype. ” But I haven’t really seen it much anywhere else. They don’t define it there either.

        So I’m operating on wikipedia’s explanation
        https://en.wikipedia.org/wiki/Subtyping
        which is that “If S is a subtype of T, the subtyping relation is often written S <: T, to mean that any term of type S can be safely used in a context where a term of type T is expected."

        This means that if struct A has all the members and member functions of struct B, then A is a subtype of B. Thus, if A alias this'es B, then it should be a subtype by definition.

        My PR is here
        https://github.com/dlang/phobos/pull/5700
        I need to update it to add attributes to the unittests, but it's in pretty good shape other than that. I purposefully ignore the member functions from object so that a struct can be a subtype of a class, so I make a small exception there.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: