I was working on a project recently and kept running into the annoying issue that, while tuples implement the equality operator, they do not implement Equatable. E.g.
struct Foo: Equatable { // error: type 'Foo' does not conform to protocol 'Equatable'
let orderedPairs: [(String, Int)]
}
So then a naive user might try to implement the equality operator themselves:
struct Foo {
let orderedPairs: [(String, Int)]
}
extension Foo: Equatable {
static func == (lhs: Foo, rhs: Foo) -> Bool {
return lhs.orderedPairs == rhs.orderedPairs // '<Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool' requires that '(String, Int)' conform to 'Equatable'
}
}
"What the hell!?" they think, "String is Equatable. Int is Equatable. But (String, Int) is not Equatable? But I can compare tuples right?" So they try again.
struct Foo {
let orderedPairs: [(String, Int)]
}
extension Foo: Equatable {
static func == (lhs: Foo, rhs: Foo) -> Bool {
return zip(lhs.orderedPairs, rhs.orderedPairs).lazy
.contains { $0 != $1 }
}
}
Finally, it works. They don't really understand why they had to jump through all these hoops and they're annoyed. Also, if the ordered pairs were a member of a much larger struct, they would have to implement a complex equality operator just to add this one field. So they try to build a reusable component.
struct Pair<A, B> {
let first: A
let second: B
}
extension Pair: Equatable where A: Equatable, B: Equatable {
static func == (lhs: Pair<A, B>, rhs: Pair<A, B>) -> Bool {
return lhs.first == rhs.first && lhs.second == rhs.second
}
}
struct Foo: Equatable {
let orderedPairs: [Pair<String, Int>]
...
}
This works well enough until they want to add more elements to the tuple. At this point they could continue making more strictly typed objects, (maybe using a meta programming library like Sourcery), or they could make one object which type-erases the tuple which isn't great either.
All of these issues could be solved with Variadic Generics and a nominal Tuple type on which we could write a conditional conformance to Equatable. While I know Variadic Generics were a part of the Generics Manifesto, I was told that they're not a priority at the moment, and will likely come after ABI stability is established. I feel like this is a mistake. The language does not feel fully formed without them. Most functional programming libraries like Prelude are forced to either result to runtime hackery, or they codegen generic function overloads up to some arbitrary arity (e.g. you can curry functions of up to 4 arguments). For a language that supposedly supports FP as a first class programming paradigm, the lack of variadic generics feels like a major letdown.
In the event that we don't see Variadic Generics anytime soon, I think we need better solutions in place for working with Tuples and functions of many arguments. I'm definitely open to suggestions here, but I was thinking that at least for the time being we could make tuple a nominal type and add an Equatable conformance as a workaround in the meantime (like Array had before conditional conformances). What do you think?