Dependent types are types “that depend on values”. Say you had a function `f`

that took an integer. If you can write that function whereby it returns a value of type `A`

when that integer is even, or a type `B`

if the integer is odd, then you’re working with dependent types. (I think. I’m not sure: if I’ve gotten it wrong tweet me.)

### Dependent Pretendance

As far as I can tell, this is not possible in Swift. All variables are statically typed, and those types must be found at compile-time. As long as you’re not messing around with casting:

struct A {} struct B {} func f(i: Int) -> Any { return i % 2 == 0 ? A() : B() }

You won’t be able to manage it.

Now, sum types can give you something that *looks* like dependent types:

struct A {} struct B {} enum SumType { case Even(A), Odd(B) } func f(i: Int) -> SumType { return i % 2 == 0 ? .Even(A()) : .Odd(B()) }

But that doesn’t fit the description: the thing returned is of type `SumType`

, *not* `A`

or `B`

.

That’s fine, though. As with all of these highfalutin mathematical concepts in programming, you can steal some of the cool and fun *patterns* from your Haskells and Lisps and Idrises and implement them in whatever language you want.

As it happens, implementing this stuff in Swift gets you even *further* away from the formal definition of dependent types. Instead of allowing types to be decided at runtime, you end up forcing even *more* resolution and computation to happen at compile-time. Take “numbers-as-types”, for instance:

protocol Nat { init() } struct Zero : Nat {} protocol NonZero: Nat { typealias Pred: Nat } struct Succ<N : Nat> : NonZero { typealias Pred = N }

Once you encode some numbers by hand:

typealias One = Succ<Zero> typealias Two = Succ<One> typealias Three = Succ<Two> typealias Four = Succ<Three> typealias Five = Succ<Four> typealias Six = Succ<Five> typealias Seven = Succ<Six> typealias Eight = Succ<Seven> typealias Nine = Succ<Eight>

You get thinking about exactly *how much* computation you can achieve at compile time:

Sum<One, Two>.Result // Three Comp<Five, Nine>.Result // LT Comp<Four, Four>.Result // EQ

### Sum types, divide types, multiply types

What I wanted, ideally, was some basic “Algebraic data types”. (Today. Today was the day I made the worst pun.) I wanted to be able to add the type `One`

to the type `Two`

and get the type `Three`

. Once you can manage those, multiplication, division and all kinds of silliness are possible. I set myself some rules: all calculations must be performed at compile-time, and all calculations must work with arbitrary values.

I’ve not been able to manage, unfortunately. If someone could figure out how to do it, I would love to hear it. I’ve been stealing ideas from Faking It: Simulating Dependent Types in Haskell mainly.

Here’s the kind of code that made me think it was possible:

let ar = [1, 2, 3, 4, 5].reverse() let se = AnySequence([1, 2, 3, 4, 5]).reverse()

The types returned by those two methods are different. This is all to do with that protocol-oriented-programming business: the compiler will try to select the most specialised version of a method to use. So in the example above, since an array can just be indexed backwards, the compiler uses a method that returns a lazy `ReverseRandomAccessCollection`

. However, for the `AnySequence`

, the `reverse`

method has to create a whole new array.

With that in mind, we can make a protocol:

protocol BinaryOp { typealias A: Nat typealias B: Nat }

Then, we can extend it, like this:

struct EQ {} extension BinaryOp where A == B { typealias Result = EQ }

So far, so good! The compiler will add that method to all types that conform to the `where`

clause. So if there is a concrete type that conforms to `BinaryOp`

:

struct Comp<E0: Nat, E1: Nat> : BinaryOp { typealias A = E0 typealias B = E1 }

Only instances where `A`

and `B`

are equal will get the type alias:

Comp<One, One>.Result Comp<One, Two>.Result // Error

But that’s not ideal. We want something that returns `NEQ`

when the types are not the same. Easy enough, right?

struct NEQ {} extension BinaryOp { typealias Result = NEQ }

But there’s an error: `invalid redeclaration of 'Result'`

. The compiler won’t allow polymorphism with typealiases. It *does* allow polymorphism with properties, though:

extension BinaryOp { var r: EQ { return EQ() } } extension BinaryOp where A == B { var r: NEQ { return NEQ() } }

This is already a less elegant solution than the typealiases, since we’re going to have to initialise things. All of the type information is available at compile-time, though, so I’ve not broken any of my rules.

Comp<One, One>().r // EQ Comp<One, Two>().r // NEQ

How about something more complex? Instead of `EQ`

and `NEQ`

, maybe `LT`

, `GT`

, and `EQ`

?

It’s hard to see how it would work. Well, here’s the base case:

extension BinaryOp where A == B { var r: EQ { return EQ() } }

Then, any non-zero is bigger than zero:

struct LT {} extension BinaryOp where A == Zero, B : NonZero { var r: LT { return LT() } } struct GT {} extension BinaryOp where A : NonZero, B == Zero { var r: GT { return GT() } }

If both `A`

and `B`

are nonzero, they should have a `Pred`

typealias, which we can use, recursively:

extension BinaryOp where A : NonZero, B : NonZero { var r: ?? { return Comp<A.Pred, B.Pred>().r } }

This doesn’t work. I’m fairly sure this is a definitive dead end. Here’s the error: `ambiguous reference to member 'r'`

. The problem is that that error encapsulates exactly what I’m trying to achieve: I *want* the reference to be ambiguous, so it *depends* on the types of `A`

and `B`

. Most other routes I went down hit similar roadblocks:

protocol BinaryOp { typealias A: Nat typealias B: Nat typealias Result var r: Result { get } }

The idea here was that you could have various implementations of `r`

, so that the `Result`

typealias would be inferred. The problem is the compiler wants to figure out what `Result`

is when you make a type that conforms to the protocol, so every type will get the default implementation.

Yet more versions I tried all hit the `ambiguous`

error, which makes me think this kind of thing is fundamentally impossible in Swift’s current form.

So I’ve got to break one of the rules: no more arbitrary numbers.

struct AddOne<N : Nat> { typealias Result = Succ<N> } struct AddTwo<N : Nat> { typealias Result = Succ<AddOne<N>.Result> }

And so on. Or:

extension Binary where A == B { var sub: Zero { return Zero() } var com: EQ { return EQ() } } extension Binary where A == Succ<B> { var sub: One { return One() } var com: GT { return GT() } }

Which can give you subtraction.

### Let’s Pretend to be Useful

All of that stuff is interesting, but very *very* far from being useful.

The length-indexed list from the other day probably is useful, though. As well as being kind of cool and safe, there are some (minor) optimisations it can do.

The other dependent type staple is the heterogenous list.

Now, this isn’t just any heterogenous list: we’re not writing Python here. This is a *statically typed* heterogenous list. Swift has a construct very similar to this already: a tuple!

But tuples aren’t very extensible:

extension Tuple where First : Comparable {... extension Tuple where Count == Two {...

And you can’t work with them in terms that most lists can:

(1, "a", 2.0) + ("b", -3)

So that’s where another tuple type can come in. A la Rob Rix, we could make a right-recursive tuple, terminated by `()`

. There’ll be one overarching protocol:

protocol _AnyTuple : CustomStringConvertible { var tDesc: String { get } var count: Int { get } typealias Arity : Nat }

And the empty tuple:

struct EmptyTuple {} extension EmptyTuple : _AnyTuple { var description: String { return "()" } var tDesc: String { return ")" } var count: Int { return 0 } typealias Arity = Zero }

The descriptions are just there to give us a pretty printout. Here’s the tuple struct:

struct NonEmptyTuple<Element, Tail : _AnyTuple> { var (head, tail): (Element, Tail) } extension NonEmptyTuple : _AnyTuple { var count: Int { return tail.count + 1 } var description: String { return "(" + String(reflecting: head) + tail.tDesc } var tDesc: String { return ", " + String(reflecting: head) + tail.tDesc } typealias Arity = Succ<Tail.Arity> }

Now, to build a tuple. Since it’s right-recursive, it might look like this:

1 , "a" , 4.0 , ()

But there are two problems with that: first, the comma is not overloadable. That’s probably a good thing. Second, it doesn’t really look like a tuple.

Joe Groff solved the first problem (albeit by committing a mortal sin). Just use a unicode comma! The only one I could find that works has the delightful name of Hypodiastole.

infix operator ⸒ { associativity right precedence 90 }

Trying to find it in the character viewer each time was a pain, though. So I went with the boring vertical bar.

The second problem can be solved with some sneaky overloading. Here’s what these functions look like:

infix operator | { associativity right precedence 90 } func |<E, T:_AnyTuple>(lhs: E, rhs: T) -> NonEmptyTuple<E, T> { return NonEmptyTuple(head: lhs, tail: rhs) } func |<E, T>(lhs: E, rhs: T) -> NonEmptyTuple<E, NonEmptyTuple<T, EmptyTuple>> { return NonEmptyTuple(head: lhs, tail: NonEmptyTuple(head: rhs, tail: EmptyTuple())) }

We can now, finally, build a Tuple:

(1 | 2.0 | "a" ) // (1, 2.0, "a")

One little wrinkle with protocols, though. If you try this:

extension NonEmptyTuple where Arity == Two {...

There’s an error: `neither type in same-type refers to a generic parameter or associated type`

. Generally speaking, `==`

requirements in struct extensions don’t work. However, they do work on protocols. So a wrapper protocol is needed:

protocol Tuple : _AnyTuple { typealias Head typealias Tail : _AnyTuple typealias Arity : NonZero var head : Head { get } var tail : Tail { get } } extension NonEmptyTuple : Tuple {}

Alright. Time to work with it.

extension Tuple where Head : IntegerArithmeticType, Tail : Tuple, Tail.Head : IntegerArithmeticType, Arity == Two { func matSum(with: Self) -> NonEmptyTuple<Head, NonEmptyTuple<Tail.Head, EmptyTuple>> { let a = head + with.head let b = tail.head + with.tail.head return (a | b) } } (1 | 4).matSum(3 | 2) // (4, 6)

The basic advantage of this heterogenous list in Swift is its extensibility: you can treat tuples of length 2 as a type, or tuples where the third element is comparable as a type, and so on.

extension Tuple where Tail : Tuple, Tail.Head : Comparable { func isSecondLessThan <T : Tuple where T.Tail : Tuple, T.Tail.Head == Tail.Head> (with: T) -> Bool { return tail.head < with.tail.head } } let a = (1 | 3.0 | "a" | 43) let b = ("c" | 4.0 | 1) a.isSecondLessThan(b)

Most of this stuff is madness. The custom infix unicode operator should have tipped you off to that: but it’s not to say that *nothing* here is useful. Compile-time warnings are great. I think the fixed-length array works. But this tuple stuff is too hacky: it only becomes useful if there are some low-level changes to the language.

What’s really useful, though, is *thinking* about types with dependency in mind. Getting familiar with what is and isn’t possible to write between the `where`

and the `{`

in an extension gives you a good idea of how powerful protocols and their specialisations are.

For some extra reading, check out DependentHaskell, Heterogenous Collections in Haskell, and Strongly Typed Heterogenous Collections. I’m muddling my way through seeing what’s possible with length-indexed lists, heterogenous lists, and numeral types over here, if you’re interested.