Swift: Inheritance and generics in algorithms


Swift algorithms rely heavily on generics, but being generic doesn't necessarily mean that an algorithm can accept any instance type. Often the instance types that can be accepted by an algorithm are restricted to ones that adopt or inherit a certain protocol. For example extend() requires that the instance being extended adopts the Sequence protocol (S : Sequence) and that the extension is made up of elements of the same type as contained in the sequence being extended (hence T == T).
func extend<S : Sequence where T == T>(sequence: S)
So how do we know that extend(), for example, can accept both an array and a string? Well, the Array type adopts the MutableCollection protocol, which in turn adopts the Collection protocol, which finally adopts the Sequence protocol. This means that the Array type inherits the Sequence protocol down the chain and so we can do this:
var array = ["one","two"] array.extend(["three","four"])
Likewise String adopts the Collection protocol and, as before, this in turn adopts the Sequence protocol. Again we have inheritance of the Sequence protocol and so can do this:
var string = "Hello," string.extend(" #Swift!")
The result is an algorithm capable of performing the equivalent operation on more than one instance type. Or so it would seem, but that isn't the whole story.

More detail

Although, this blogpost has explained extends() as a generic algorithm, in actual fact to confuse matters extend() is a method with separate declarations within the Array type and the String type. The opening example is the Array method and the String method looks like this:

func extend<S : Sequence where Character == Character>(sequence: S)

Both declarations leverage generics in the use of a generic syntax (angle brackets surrounding the declaration of S) and share a pattern (not to mention a name), but they are declared separately (within the body of the Array and String types). And here, in the String version of extend(), the specific use of Character narrows the method to the String type as the basis of the Sequence. This is unlike split()sorted()swap(), and so on, which float free of the bodies of classes, structs and enums, and might be thought of as even more "generic" in style.

But generics are not about whole methods or algorithms being capable of accepting and/or returning multiple (and unspecified) types but about the syntax and usually about that syntax enabling at least one element to have the quality of being generic.

In the Array and the String version of the extend method we feel a little cheated because although there is the requirement of a type that adopts or inherits the Sequence protocol, it can mean only one thing in the respective cases (an array within the context of the Array type and a string within the context of the String type). And while the Array version does have the saving grace of enabling arrays with generic types within them, the String version is tied to Characters as elements in the sequence. So why not simply have a method written like this:

func extend(sequence: String)

But once again generics are about leveraging the generic syntax, and so we come back to this as justification for including extend() under the heading of generics. Rather than excluding it based on some subjective concept of what being generic actually means, or thoughts about how methods might be rewritten to remove the syntax of generics.

Conclusion

While we might speculate (as further justification) that the generic syntax employed suggests that either at a lower level the extend() methods leverage some shared code or at some point they were combined or at some point in the future they might converge, this is mere speculation, and the real point of this post has been to look at the generic syntax and the concept of inheritance, rather than make deeper explorations of what it means to be generic and the lower level workings of Swift.

Comments