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.
> 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.
1: https://github.com/SpongePowered/Mixin/wiki/Introduction-to-...
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.
var _ AssertedInterface = &MyType{}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.
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.
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'));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.
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.
#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/vPhf13xEhMoreover, 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.