Swift associated types are a bit confusing, and Russ Bishop’s article on the subject is top notch, the best I’ve seen. I do have a couple of questions after reading it, though:
Type parameters force everyone to know the types involved and specify them repeatedly (when you compose with them it can also lead to an explosion in the number of type parameters).
Why would this need to be the case, necessarily? In Java, a language that does allow for parameterized protocols (Java calls them “interfaces”) you can create concrete classes that conform to generic interfaces without specifying the parameterized type each time the concrete class name is referred to.
interface Food {
}
interface Animal<T extends Food> {
public void eat(T food);
}
class Grass implements Food {
}
class Cow implements Animal<Grass> {
public void eat(Grass grass) {
// Eat the grass
}
}
// No ugly repeating of the concrete type being used
Cow cow = new Cow();
cow.eat(new Grass());
[Type parameters are] part of the public interface. The code that uses the concrete thing (class/struct/enum) makes the decision about what types to select. By contrast an associated type is part of the implementation detail. It's hidden, just like a class can hide its internal ivars.
I don’t see how associated types leak implementation details any less. Taking the example from the post:
class Cow : Animal {
func eat(f: Grass) { ... }
func supplement(s: Salt) { ... }
}
Cow
’s method signature still publically specifies that it’s eat
method can only take an argument of type Grass
. I’m confused as to how this is any more “hidden” than if generics were used instead of associated types.
That's one of the biggest issues.
Java also has the
?
type parameter specifier which gets around this issue (but IMHO in a very ugly way). Without that all the following would have to be aware of supplements even though they don't care, or you must break up the interface even if it logically belongs together.buyFoodAndFeed
has to know thatAnimal
has type parameters and that it has two of them, then tell the compiler it doesn't care about one of them. Those are details it doesn't really care about.If you want a
Feeder
interface and implementation, the type parameters begin to multiply:With associated types you can reference the abstract type members, so there would be no need in
StoreFeeder
to specifyGrass
three times for example. And again without?
here the interface and class must be aware of theSupplement
concept they care nothing about.As a separate issue try making
Cow<T> implements Animal<F,S>
and see where that maze leads you. Or try addingpublic Excrement poop()
toAnimal
and notice all the places you now have to change the number of type parameters. Again you can bypass this by breaking up the interface, though that won't help you if you want to constrain the types which you can do easily with associated types (typealias T2 = T
)If the type system is fully-featured enough, you can express any algorithm in terms of type members or type parameters. Neither Swift nor Java meet that criteria though.
Personally I'd like to see protocols supporting type parameters, as well as declaring locals/parameters as protocols with generic constraints.