Here is Felix code, in the library, which provides equality. I will explain below the code. Swift could do this too with the right enhancements, and do it simpler for a reason to be explained.
instance [T,U with Eq[T], Eq[U]] Eq[T ** U] {
fun == : (T ** U) * (T ** U) -> bool =
| (ah ,, at) , (bh ,, bt) => ah == bh and at == bt;
;
}
instance[t,u with Eq[t],Eq[u]] Eq[t*u] {
fun == : (t * u) * (t * u) -> bool =
| (x1,y1),(x2,y2) => x1==x2 and y1 == y2
;
}
instance[t with Eq[t]] Eq[t*t] {
fun == : (t * t) * (t * t) -> bool =
| (x1,y1),(x2,y2) => x1==x2 and y1 == y2
;
}
So here's the explanation. First, the type A * B * C etc is the type of a tuple. The cartesian product is an N-ary operator, not a binary operator. However there is a second type for a tuple, written with two stars. A ** B * C, and ** is a right associative binary operator. Values can be written in the form (1,,"hello",,X) for example, where X is a tuple. There's a Felix specific gotcha: in Felix there are no tuples of one component. Three instances are required because of this. In Swift, you only need two, one to handle the recursion, and one to handle the base case, the unit tuple ().
An instance is an implementation of a protocol (type class in Haskell, class in Felix). The functions use a pattern match in Ocaml/ML style. So the rule for equality is given by:
| (ah ,, at) , (bh ,, bt) => ah == bh and at == bt;
which simply reads, two tuples a and b are equal is the head components are equal and the tail is equal. The "with" clause is Haskell and Swift "where" clause.
Just to reinforce it, the recursion for less than is given by:
instance [T,U with Tord[T], Tord[U]] Tord[T ** U] {
fun < : (T ** U) * (T ** U) -> bool =
| (ah ,, at) , (bh ,, bt) => ah < bh or ah == bh and at < bt;
;
}
This is user code in the library. The primary requirement is that tuples have a recursive representation. One also needs polymorphic recursion to be unrolled during monomorphisation.
Anyhow the point is that this can be done for any function whose codomain is in variant. In other words a function T -> X where T is generic and X is fixed, can be extended to a tuple by unpacking the tuple into a head and tail, applying the function to the head, and then folding that into the function applied to the tail. Its a boilerplate, in which the folding is the part the user has to write. The fold for equality is just logical conjunction, for less than the fold is a bit harder.
Getting this to work in Swift is a lot more ambitious than just adding equality for tuples. The plus side is that it allows the user to make tuples conform to protocols they invented themselves, which the library or compiler can obviously never do.