How to convert [1, 2, 3] (Int array of three elements) to a tuple of three elements in a functional way?

I want:

[1, 2, 3].asTuple
// (1, 2, 3)

Since I don't know if the above is possible, I add:

extension Array {
  var asTuple: (Element, Element, Element) {
    precondition(self.count == 3, "Array must contains exactly 3 elements!")
    return (self[0], self[1], self[2])
  }
}

Is there clever built-in way to do this?

1 Like

Why do you need it as a tuple? That asTuple property is dangerous — it crashes if the array doesn’t have exactly three elements — but I’m not sure it’s even necessary.

I just want to know if there is straight forward way to do this.

I found this:

extension Array {
    func splat() -> (Element,Element) {
        return (self[0],self[1])
    }

    func splat() -> (Element,Element,Element) {
        return (self[0],self[1],self[2])
    }

    func splat() -> (Element,Element,Element,Element) {
        return (self[0],self[1],self[2],self[3])
    }

    func splat() -> (Element,Element,Element,Element,Element) {
        return (self[0],self[1],self[2],self[3],self[4])
    }
}

//              vvvvv<-- must provide type annotation, if not, splat() is ambigious
let blahTuple: (Int, Int, Int) = [111, 222, 333].map { $0 }.splat()

print(blahTuple)  // (111, 222, 333)

Parameter packs get you close, but I'm afraid you need same-element requirements:

extension Collection {
  // error: Same-element requirements are not yet supported
  func asTuple<each T>() -> (repeat each T) where repeat each T == Element {
      var slice = self[...]
      return (repeat slice.removeFirst())
  }
}

You can see it kinda work if you supply that constraint externally (maybe there's a more elegant bodge than this):


extension Collection {
  func asTuple<each T>(_ types: repeat (each T).Type) -> (repeat each T) {
      var slice = self[...]
      return (repeat slice.removeFirst() as! each T)
  }
}


let (x,y,z) = [1,2,3].asTuple(Int.self,Int.self,Int.self)

print(x,y,z) // prints 1 2 3
4 Likes

That was about as far as I made it too. You can clean it up a bit by passing a tuple metatype rather than a tuple of metatypes, (i.e. (Int, Int, Int).self rather than Int.self, Int.self, Int.self), and with the following monstrosity it suffices to only pass the correct shape while also avoiding the force cast:

enum E<First, Second> {
    typealias First = First
    typealias Second = Second
}

extension Collection {
    func asTuple<each T>(_ s: (repeat each T).Type) -> (repeat E<Element, each T>.First) {
      var slice = self[...]
      return (repeat (slice.removeFirst(), (each T).self).0)
  }
}

let (x, y, z) = [1, 2, 3].asTuple((Any, Any, Any).self)

I wouldn't call this 'more elegant' by any means, though. :slight_smile:

1 Like

Sure, but I’ve rarely had the need to convert between arrays and tuples. I’m asking in case that your use case might have a different approach that doesn’t need a tuple — or perhaps does need a tuple but not an array — hence my request for more information.

"Why" is a valid question here. After a certain size tuples are impractical: we don't have some magic (Int*1234) to represent (Int, Int, .... 1234 Ints in total) so it won't be possible creating a large tuple statically (e.g. so it is allocated on the stack) and if you have to allocate it dynamically you could as well use a more convenient thing be it an array or just a block of dynamically allocated memory and treat that as a vector of N elements. And for short tuples it won't be a huge problem repeating a few "splats" as in above. There is no elegant solution but there is no need for it either.

If to add anything I'd first do the (T * N) as a shorthand tuple type notation, provided or providing the language would be comfortable handling large tuples (last time I checked the compilation time for a sample app that was using a large tuple with a few hundred elements was huge.

1 Like

A generic way without knowing the length would be very helpful for interfacing with C APIs, where variable length arrays are mapped to Swift tuples.

So far I don’t see any way to construct a tuple with runtime variable size. It seems you have to let the compiler know how big is the tuple size.

In that case I'd do something like this:

// using `in6_addr.init(_ string: String)` in this example
// from https://forums.swift.org/t/how-to-load-components-of-ipv4address/67739/5

var tuple = in6_addr("1:2:3:4:5:6:7:8").__u6_addr.__u6_addr8

var array = [UInt8](repeating: 0, count: MemoryLayout.size(ofValue: tuple))
withUnsafeBytes(of: &tuple) { tupleBuffer in
    array.withUnsafeMutableBytes { arrayBuffer in
        arrayBuffer.copyMemory(from: tupleBuffer)
    }
}
print(array) // [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]

Alternative simpler version:

let array = withUnsafeBytes(of: tuple) {
    [UInt8]($0)
}

A slightly modified version that uses return type inference and allows you specify desired target array type only once at the point of use:

// generic
func makeArray<T, R>(from tuple: R) -> [T] {
    withUnsafeBytes(of: tuple) { buffer in
        [T](buffer.assumingMemoryBound(to: T.self))
    }
}

let array8: [UInt8] = makeArray(from: tuple)
print(array8) // [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]
let array16: [UInt16] = makeArray(from: tuple)
print(array16) // [256, 512, 768, 1024, 1280, 1536, 1792, 2048]
1 Like

Aren't tuples, by definition, statically structured objects? A tuple with runtime variable size is an array, isn't it?

2 Likes

(N x T) would look nicer. :slight_smile:

let u : (N x T) = (N x T)({value ($0)}) // value : (Int) -> T

// i th element
let i = ... // [0, N)
let u_i = u [i]
1 Like