Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The difference between Go and Java is that in Go a type need not declare its adherence to an interface up front—any type that has methods of appropriate names and signatures is considered to implement the interface, even if its designers were not aware of the interface’s existence. (This involves a small bit of dynamism in the runtime; easily cached, though, as the set of methods of a given type and the set of all interfaces are both fixed by the time the program runs.) Whether that’s a good thing depends on your design sensibilities (ETA: nominal vs structural).


For those wishing Java had a similar feature, there's Manifold: https://github.com/manifold-systems/manifold/tree/master/man...

Manifold is a very interesting project that adds a lot of useful features to Java (operator overloading, extension classes, and a whole bunch more). I don't know if it's smart to use it in production code because you basically go from writing Java to writing Manifold, but I still think it's a fun project to experiment with.


http://manifold.systems/

> Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.

Neat tool. It is like having a programmable compiler built into your language.


The funny thing about Java is that while its design is to be entirely nominally typed, the way it is implemented in the JVM is compatible with structural typing, but there are artificial limitations set to follow the intended design (though of course, if one were to disable these limitations then modeled type safety goes out of the window as Java was simply not designed to be used that way). One community which takes advantage of this fact is the Minecraft modding space, as it is the basis[1] of how modding platforms like Fabric work.

1: https://github.com/SpongePowered/Mixin/wiki/Introduction-to-...


>The difference between Go and Java is that in Go a type need not declare its adherence to an interface up front.

Go can't declare adherence up front, and in my view that’s a problem. Most of the time, explicitly stating your intent is best, for both humans reading the code and tools analyzing it. That said, structural typing has its moments, like when you need type-safe bridging without extra boilerplate.


You can assert that your type implements an interface at compile time, though, e.g.

    var _ AssertedInterface = &MyType{}


One of the main uses for interfaces in Go is defining the contact for _your_ dependencies. Rather than saying your function takes a socket, if you only ever call Write(), you can take a Writer, or define another interface that is only the set of functions you need. This is far more powerful than declaring that your type implements an interface up front. It allows for things like e.g. multiple image libraries to implement your interface without knowing it, enabling your project to use them interchangeably. And as another commenter said, you can have the compiler verify your compliance with an interface with a simple (though admittedly odd looking) declaration.


> It allows for things like e.g. multiple image libraries to implement your interface without knowing it

That virtually never happens. Seriously, what would be the odds? It’s so much more usual to purposefully implement an interface (eg a small wrapper the writer thingy that has the expected interface) than to use something that happens to fit the expected interface by pure chance.

It’s not a structural vs nominal problem but other, typescript is structural but has the implements keyword so that the interface compliance is checked at declaration, not at the point of use. You don’t have to use it and it will work just like Go, but I found that in 99% of cases it’s what I want: the whole point of me writing this class is because I need an interface implementation, might as well enforce it at this point.


It happens all the time because e.g. third party developers follow the same patterns in the standard lib. And it was designed that way. Another example: logging libs are frequently quite similar. Even though the stdlib didn’t define that interface, you can still make your own that works for lots of libs. But even if you don’t use that part of it, it’s still great to limit the scope of your dependency. Less to mock, more flexible code. You obviously don’t like this and I’m not going to convince you, I think. But this is really one of the best features of Go.


Example: https://pkg.go.dev/image

This library (and some 3rd parties) have a lot of implementations of image types. But without any inheritence or interface declaration on their part I can make something that will do image size calculations by defining an interface that only contains

Bounds() Rectangle

Now, any of those types can be passed into my function to do checking.

Another example. The stdlib log package didn't define an interface (they should have) and the stdlib logger looks like this: https://pkg.go.dev/log

However, lots of people have made drop in replacements that implement the same method signature. By defining an interface you can swap in the stdlib logger or a 3rd party without the stdlib implementing that interface.

So yeah, the stdlib implemented the interface I defined in my code "by accident" and that's a great thing.


I don’t agree it’s a structural VS nominal difference. Typescript is structural, but it does have the “implements” keyword.

Which makes a million times more sense to me, because realistically when do you ever have a structure that usefully implements an interface without being aware of it?? The common use-case is to implement an existing interface (in which case might as well enforce adherence to the interface at declaration point), not to plug an implementation into an unrelated functionality that happens to expect the right interface.


TypeScript doesn't require a class to use it, though, because it's structurally typed. All that "implements Foo" in this example does is make sure that you get a type error on the definition of "One" if it doesn't have the members of "Foo".

If "Two" didn't have a "name: string" member, then the error would be on the call to "test".

    interface Foo {
        name: string
    }

    class One implements Foo {
        constructor(public name: string) {}
    }

    class Two {
        constructor(public name: string) {}
    }

    function test(thing: Foo): void {
        //...
    }

    test(new One('joe'));
    test(new Two('jane'));


GNU C++ once had this feature; it was called Signatures. It was removed, though.

A signature declaration resembled an abstract base class. The target class did not have to inherit the signature: just have functions with matching names and types.

The user of the class could cast a pointer to an instance of the class to a pointer to a compatible signature. Code not knowing anything about the class could indirectly call all the functions through the signature pointer.


Nowadays you can do that with concepts.


I don't see how concepts can emulate signatures to the full extent that the target object can be manipulated as if it conformed to an abstract base, without any wrapper object being required to handle it.

Without signatures, we have to use some kind of delegating shim which takes the virtual function calls, and calls the real object. It could be a smart pointer.

With signatures, we don't use smart pointers, just "pointer to signature" pointers. However, I suspect those pointers had to be fat! Because, surely, to delegate the signature function calls to the correct functions in the target object class, we need some vtable-like entity. The signatures feature must generate such a vtable-like table for every combination of signature and target class. But target object has no space reserved in it for that table pointer. The obvious solution is a two-word pointer which holds a pointer to the object, and a pointer to the signature dispatch table specific to the signature type and target object's class.

If we can use concepts to do this, with a smart pointer that ends up being two words (e.g. pointer to its own vtable, and a pointer to the target object), we have broken even in that regard.


Here is an example then, assuming you mean this kind of abstrations,

    #include <iostream>

    using namespace std;

    template <typename T>
    concept Speaker = requires (T t) {
        t.speak();
    };

    class Duck {
        public:
        void speak() const {
            cout << "quack";
        }
    };

    class Dog {
        public:
        void speak() const {
            cout << "auau";
        }
    };

    class Cat {
        public:
        void speak() const {
            cout << "miau";
        }
    };

    template<Speaker T>
    void speaking_animal(const T&  animal) {
        animal.speak();
        cout << "\n\n";
    }

    template<Speaker... T>
    void speaking_farm(const T&... animals) {
        auto space_adder = [&](auto creature) -> void {
            creature.speak();
            cout << " ";
        };
        (space_adder(animals), ...);
    }


    int main() {
        Duck duck;
        Dog dog;
        Cat cat;

        speaking_animal(duck);
        
        speaking_animal(dog);

        speaking_animal(cat);

        speaking_farm(duck, dog, cat);

    }

Live example, https://godbolt.org/z/vPhf13xEh


Right, but speaking_animal(x) is not dynamic OOP dispatch; it's a template function that gets instantiated for each animal type.

Moreover, everything here can be done without a concept.

This version of the code builds with g++ -std=c++17. We just get worse diagnostics if we try to use something as a Speaker which doesn't conform.

    #include <iostream>

    using namespace std;

    class Duck {
        public:
        void speak() const {
            cout << "quack";
        }
    };

    class Dog {
        public:
        void speak() const {
            cout << "auau";
        }
    };

    class Cat {
        public:
        void speak() const {
            cout << "miau";
        }
    };

    template<typename T>
    void speaking_animal(const T&  animal) {
        animal.speak();
        cout << "\n\n";
    }

    template<typename... T>
    void speaking_farm(const T&... animals) {
        auto space_adder = [&](auto creature) -> void {
            creature.speak();
            cout << " ";
        };
        (space_adder(animals), ...);
    }


    int main() {
        Duck duck;
        Dog dog;
        Cat cat;

        speaking_animal(duck);
        speaking_animal(dog);
        speaking_animal(cat);
        speaking_farm(duck, dog, cat);
    }
I was thinking about more something along these lines. But note the double indirection: we end up passing the smart pointer animal_pointer by reference.

We achieve the "signature thing" though in that we take these animal objects and effectively get them to to conform to the common animal_pointer abstract base without their cooperation.

    #include <iostream>

    using namespace std;

    class Duck {
    public:
        void speak() const { cout << "quack"; }
    };

    class Dog {
    public:
        void speak() const { cout << "auau"; }
    };

    class Cat {
    public:
        void speak() const { cout << "miau"; }
    };

    class animal_pointer {
    public:
        virtual void speak() const = 0;
    };

    template <typename T> class animal_pointer_impl : public animal_pointer {
    private:
        T *obj;
    public:
        animal_pointer_impl(T *o) : obj(o) { }
        virtual void speak() const { obj->speak(); }
    };

    void animal_api(const animal_pointer &p)
    {
        p.speak();
 cout << '\n';
    }

    int main() {
        Duck duck;
        Dog dog;
        Cat cat;

        animal_pointer_impl<Duck> p0(&duck);
        animal_pointer_impl<Dog> p1(&dog);
        animal_pointer_impl<Cat> p2(&cat);

        animal_api(p0);
        animal_api(p1);
        animal_api(p2);
    }
animal_api is a regular function, which represents some external API that we don't get to recompile.


You missed speaking_farm().


So golang supports 'duck typing'?


I think in a static context, it's generally referred to as structural typing, but yeah.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: