A tuple conformance idea

This just popped into my head today. What if we could combine the ideas of:

  • withoutActuallyEscaping(_: do:)
  • AnyHashable

to work around slapping protocols onto tuples?

First, we make an AnyEquatable:

public struct AnyEquatable {
    public init<E: Equatable>(_ base: E) {
        //...
    }

    public var base: Any { /*...*/ }
}
extension AnyEquatable: Equatable {
    public static func == (lhs: AnyEquatable, rhs: AnyEquatable) -> Bool { /*...*/ }
}
extension AnyEquatable: CustomStringConvertible {
    public var description: String { /*...*/ }
}
extension AnyEquatable: CustomDebugStringConvertible {
    public var debugDescription: String {/*...*/ }
}
extension AnyEquatable: CustomReflectable {
    public var customMirror: Mirror { /*...*/ }
}

Then, instead of slapping protocols onto tuples, we just pretend that we did:

func feigningEquatable<variadic T, R>(with tuples: [(T)], do body: ([AnyEquatable]) throws -> R) rethrows -> R
func feigningEquatable<variadic T, R>(with tuples: (T)..., do body: ([AnyEquatable]) throws -> R) rethrows -> R

We can carry this onto Hashable:

func feigningHashable<variadic T, R>(with tuples: [(T)], do body: ([AnyHashable]) throws -> R) rethrows -> R
func feigningHashable<variadic T, R>(with tuples: (T)..., do body: ([AnyHashable]) throws -> R) rethrows -> R

Since it doesn't have Self or associated type requirements, Encodable is easier:

func feigningEncodable<variadic T, R>(with tuple: (T), do body: (Encodable) throws -> R) rethrows -> R

One more to go, but Decodable wouldn't take a tuple, but give one out! We need something else:

func decodeCompound<variadic T>(as type: (T).Type, with decoder: Decoder) throws -> (T)

I use "compound" in the name in case we add more compound types:

func decodeCompound<variadic T>(as type: (;T;).Type, with decoder: Decoder) throws -> (;T;)  // sum-tuple
func decodeCompound<T: [...; some Any]>(as type: T.Type, with decoder: Decoder) throws -> T  // fixed-size array

Actually implementing this feature will have to wait until variadic generics are done. But I wanted to put it out here while it's fresh and put it in our collective consciousness for when we are ready.

A tuple can only work with EHC if all its component nominal types (which may be nested) do so. Feigned conformance is checked at compile time. The compiler should tell the user which component(s) aren't workable.

Another part is that using compound types as properties no longer disqualify a nominal type from automatic EHC conformance; the implementation just reuses the above functions.

Is this a good (preliminary) design, both in user experience or implement-ability?

I can't say I fully understand the solution you present, or the specific problem. When it comes to tuples and protocols, I do think there is an inconvenience there, but I have another solution: make it possible to use tuples as "struct literals". At least for me, the reason I want to conform tuples to protocols is that I have a tuple that I want to use as a dictionary key for example, and thus it needs to be Hashable. But the reason I use tuples is really only because they are more concise to initialise, other than that they are just structs. So you could just define

struct MyKey: Hashable, TupleLiteralConvertible {
    let x: Int
    let y: Int
}

and then at the use site

myDictionary[(4, 5)] = "some string"

instead of

myDictionary[MyKey(x: 4, y: 5)] = "some string"

This would make my life much better. I even find myself trying to use this syntax from time to time, because it's so natural.

I think tuples are fundamentally simple, so it seems counterintuitive to let them implement protocols etc. If you are going through the trouble of writing methods on them, they are structs in all but name.

1 Like

That's why I posted this alternative to slapping protocols on tuples. Like with non-escaping functions, where we don't have to come up with a conversion apparatus since we fake it with withoutActuallyEscaping, we don't add protocols to tuples but fake it with my new functions. My functions don't cover your case, where the tuples need to be persistently Hashable, but either your tuple literal idea and/or a wrapper struct (maybe private to Dictionary and Set) could help.