Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: statically "append" to tuple type

I have a templated struct in a Swift library I am writing. This struct has two characteristics:

  • Every struct "wraps" or "represents" another arbitrary type. A Foo<T> wraps a T
  • These structs can be "combined" to form a third struct, whose represented type should be a "combination" (read: tuple) of the first two. In another worlds, if fooA: Foo<A> and fooB: Foo<B>, then fooA + fooB should be of type Foo<(A, B)>.

This works well enough when there are only two types to combine, but when you chain this combination operation you start getting nested tuples, which is not what I want. For example, in the following code:

let a = Foo<A>(/* initialize */)
let b = Foo<B>(/* initialize */)
let c = Foo<C>(/* initialize */)

let d = a + b // type is Foo<(A, B)>
let e = d + c // type is Foo<((A, B), C)>

d has type Foo<(A, B)>, which is what we want, but e has type Foo<((A, B), C)>, which is an extra level of unwanted nesting.

I need some way to express that the combination of a Foo<A> and a Foo<B> is not a Foo<(A, B)>, but rather a Foo<A + B>, where + is a hypothetical static operation which means "if the first type is a tuple type, append the second type onto it, yielding a new, non-nested tuple type. If it is not a tuple type, simply make the tuple type (A, B).

This feels like pushing to compiler to (beyond?) its limits, and I suspect that it might not be possible with Swift's current templating capabilities and type system. Still, if anyone can offer a workaround, a redesign that doesn't encounter this problem in the first place, or something conceptually similar but not identical to what I'm trying to do, it would be extremely helpful. As things stand, I'm at an impasse.

like image 281
exists-forall Avatar asked May 09 '26 14:05

exists-forall


1 Answers

I think there is no generic way to do that.

Maybe, you can do something like this:

struct Foo<T> {
    let v: T
    init(_ v:T) { self.v = v }
}

func +<A,B>(lhs: Foo<(A)>, rhs:Foo<(B)>) -> Foo<(A,B)> { return Foo(lhs.v.0, rhs.v.0) }

func +<A,B,C>(lhs: Foo<(A,B)>, rhs: Foo<(C)>) -> Foo<(A,B,C)> { return Foo(lhs.v.0, lhs.v.1, rhs.v.0) }
func +<A,B,C>(lhs: Foo<(A)>, rhs: Foo<(B,C)>) -> Foo<(A,B,C)> { return Foo(lhs.v.0, rhs.v.0, rhs.v.1) }

func +<A,B,C,D>(lhs: Foo<(A,B,C)>, rhs: Foo<(D)>) -> Foo<(A,B,C,D)> { return Foo(lhs.v.0, lhs.v.1, lhs.v.2, rhs.v.0) }
func +<A,B,C,D>(lhs: Foo<(A,B)>, rhs: Foo<(C,D)>) -> Foo<(A,B,C,D)> { return Foo(lhs.v.0, lhs.v.1, rhs.v.0, rhs.v.1) }
func +<A,B,C,D>(lhs: Foo<(A)>, rhs: Foo<(B,C,D)>) -> Foo<(A,B,C,D)> { return Foo(lhs.v.0, rhs.v.0, rhs.v.1, rhs.v.2) }

// ... as many as you want ...

let f1 = Foo<(Int, UInt)>(1, 2) + Foo<String>("string") // -> as Foo<(Int, UInt, String)>
like image 124
rintaro Avatar answered May 12 '26 06:05

rintaro