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.
@russbishop You're using type parameters unnecessarily there. Type parameters and associated types can coexist (just look at Rust). Associated types make sense when there's only one logical choice for the type. Type parameters make sense when multiple implementations are reasonable. And this is precisely the rule Rust uses; a given trait can be implemented multiple times with different type parameters on a given type, but a trait can only be implemented once for any given set of type parameters (meaning a trait with no parameters can only be implemented once).
In the case of
Animal
, if an animal should only ever eat one food, then using an associated type makes sense. But if it can eat multiple foods, then type parameters make sense. In your example you're using type parameters, and claim that it's unnecessary, but if you used associated types then all of your animals could only eat one food. Granted, implementing an interfaceAnimal
several times with different foods seems perhaps a bit odd, especially if the animal only ever wants one supplement, and especially if the supplement choice is unrelated to the food choice. But at this point I think the fairly contrivedAnimal
example is starting to break down.I fully agree. I want Swift's protocols to have the full power of Rust's traits.
If protocols support type parameters, then references to those protocols must be parameterized. Are you perhaps suggesting instead that you want to be able to specify the associated type for a protocol object instead? That certainly would be nice, although I'm not sure offhand if there's any reason why it can't easily be done (note that Rust doesn't support that either; you can parameterize trait objects, but any associated types can't be expressed in the trait object and therefore any traits that have methods that use those associated types (or that use
Self
) are not "object-safe").