The Swift Type System is Not Your Friend (yet)

In OOP systems (and let’s be frank, Swift is an OOP system with functional aspirations), there are two general categories of languages. Those in which all objects descend from a single class and those which have a more disparate set of types. In .NET, Object is at the root and has a minimal set of methods for common behaviors (8 methods, to be precise). In general, there are two varieties of types in .NET: objects and value types, but all value types can be boxed into objects and can then be treated as objects.

This is not the case in Swift. Swift has the following varieties of types:

  • Value types (scalars, structs, enums, optionals, exceptions)
  • Reference types (objects)
  • Protocol lists
  • Tuples
  • Closures
  • Metatypes

Unfortunately, these types don’t always share things in common. Commonality and uniformity are a nice thing across members of a type system, which is why I prefer the .NET/JVM monolithic type system.

Today, in particular, I’m going to pick on the Swift Dictionary type. A dictionary is a mapping of keys to values using a mapping function of some kind, usually a hash function. In .NET/JVM, this is easy because every object implements Equals() and GetHashCode()/HashCode(), which is all you need to implement a hash table with collision resolution. In Swift, a dictionary is not simply a generic collection Dictionary<TKey, TValue>. It’s a little more complicated. TKey must conform to the protocols Hashable and Equatable (or having a global operator ==).

And that’s all well and good, but that means that you can only put things into a dictionary that implement those protocols.

Now consider that you have a system that must respond to arbitrary types and you want to build a dictionary of types mapped onto response functions. But you can’t do this. At least not directly. This is because metatypes aren’t objects and don’t adopt protocols. You’re not fully SOL because you can substitute ObjectIdentifier(metatype), but that means an extra object/value. Guh. What else can’t go into dictionaries? Tuples. Tuples can’t go into dictionaries. Probably because tuples can’t guarantee that all their elements implement Hashable and Equatable.

The problem, honestly, is entirely in the hands of the implementation of Dictionary. The dictionary implementation should really look something like this:

 

public class Dictionary<T, U> {
    public Dictionary(capacity:int, hasher: T->Int, comparer: (T,T)->Bool) { /* ... */ }
}

Now mind you, the Swift thing to do is to use a struct type with copy-on-write semantics, but the important thing is the constructor, into which you pass in your own hash function and your own comparer. With this type of constructor, you can put anything into a dictionary that you want as long as you pass in right closures. On top of that, the compiler which gives you sweet syntactic sugar on top of dictionary will still work if your type implements hashable and equatable because it can use use closures that adapt onto Hashable and Equatable.

Still, the non-uniformity of Swift types is maddening. Depending on the module writers to “just do the right thing” is exactly the wrong approach. How can we expect 3rd party authors to do the right thing when Apple doesn’t? But perhaps Apple will fix the core types. Apple has been rather blithe about breaking compatibility with every release of Swift and they could fix Dictionary, Tuple and Metatype if they wanted to.

 

One thought on “The Swift Type System is Not Your Friend (yet)”

  1. Passing the hashing function and comparator to the Dictionary constructor means that any code that gets two Dictionary values cannot statically know whether they are compatible or not. That sort of static knowledge is essential for writing correct code. Sometimes it is also essential for efficiency (for example implementing set union using a hedge union algorithm for functional data structures)

    Standard ML gets this right with generative functors – when you instantiate the Dictionary functor you give it not only the key and value types, but also the comparison operation. Two instantiations define distinct types.

    Haskell goes in a different direction (requiring unique instances for types) but the end result is the same – given two values of a polymorphic type you know that the operations on the type parameters of that type are the same.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.