Skip to content

Instantly share code, notes, and snippets.

@irace
Last active August 29, 2015 14:19
Show Gist options
  • Save irace/902cb9a66f65d4373f68 to your computer and use it in GitHub Desktop.
Save irace/902cb9a66f65d4373f68 to your computer and use it in GitHub Desktop.
Questions about Russ Bishop’s “Swift: Associated Type” article

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:

Why exactly would parameterized types need to be excessively repeated?

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());

How exactly do parameterized types leak implementation details if associated types do not?

[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.

@irace
Copy link
Author

irace commented Apr 15, 2015

As Kevin Ballard points out:

As a simplistic example, in Swift, SequenceType has an associated type for the element. If it had a parameter instead, all code that operated over sequences generically would have to add a new type parameter to their own type parameter list just to be able to reference the protocol, such as func foo<E, T: SequenceType<E>>() even if it otherwise doesn't need to care about the type being yielded by the sequence.

So the issue with redundant parameterized type specifications is when referring to the protocol, not a class that conforms to it.

@russbishop
Copy link

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.

interface Supplement {
}
interface Animal<F extends Food, S extends Supplement> {
  public void eat(F food);
  public void supplement(S supplementKind);
}
interface Store<F extends Food> {
   public F buyFood();
}

class Feeder {
  public <F extends Food, S extends Store<F>> void buyFoodAndFeed(Animal<F,?> animal, Store<F> store) {
    animal.eat(store.buyFood());
  }
}

buyFoodAndFeed has to know that Animal 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:

interface Feeder<F extends Food, A extends Animal<F,?>, S extends Store<F>> {
  public void buyFoodAndFeed(A animal, S store);
}
class StoreFeeder implements Feeder<Grass, Animal<Grass, ?>, Store<Grass>> {
  public void buyFoodAndFeed(Animal<Grass, ?> animal, Store<Grass> store) {
    animal.eat(store.buyFood());
  }
}

With associated types you can reference the abstract type members, so there would be no need in StoreFeeder to specify Grass three times for example. And again without ? here the interface and class must be aware of the Supplement 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 adding public Excrement poop() to Animal 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.

@lilyball
Copy link

@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 interface Animal 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 contrived Animal example is starting to break down.

Personally I'd like to see protocols supporting type parameters

I fully agree. I want Swift's protocols to have the full power of Rust's traits.

as well as declaring locals/parameters as protocols with generic constraints

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").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment