Shuffling optional-of-tuple into tuple-of-optionals with parameter pack

I'm currently writing some code that expects me to be able to turn Optional<(T1, T2, T3)> into (Optional<T1>, Optional<T2>, Optional<T3>), for any variable-length tuple in order to do destructuring using if-let and if-case-let bindings. I could theoretically come up with variations by hand, but since this is part of a Swift code generator I must be able to handle any crazy construction the user throws at it.

I tried to tackle this with parameter packs, but the best I can come up with still needs an indirection to populate the (nil, nil, ...) value:

func shuffle<each T>(_ tuple: (repeat each T)?) -> (repeat Optional<each T>) {
    if let tuple = tuple {
        return (repeat each tuple)
    }

    func _dummy<U>(_ any: U.Type) -> U? { nil }
    return (repeat _dummy((each T).self))
}

I'm wondering if there's a cleaner way of doing it?

2 Likes

You can use T?.none directly:

func shuffle<each T>(_ tuple: (repeat each T)?) -> (repeat (each T)?) {
    if let tuple { return (repeat each tuple) }
    return (repeat (each T)?.none)
}
5 Likes

Great solution by @xAlien95. You can also use (repeat nil as (each T)?).

3 Likes

I think the canonical name for this operation is unzip?

I was hoping this would work, but it crashes the compiler.

func unzip<each T>(_ tuple: (repeat each T)?) -> (repeat (each T)?) {
  tuple ?? (repeat nil as (each T)?)
}

You can work around it with this, though:

func unzip<each T>(_ tuple: (repeat each T)?) -> (repeat (each T)?) {
  tuple.map { (repeat each $0) } ?? (repeat nil as (each T)?)
}

Unfortunately, the same trick doesn't work on a method, which this probably should be*.

Crashes:

extension Optional {
  func unzip<each WrappedElement >() -> (repeat (each WrappedElement)?)
  where Wrapped == (repeat each WrappedElement) {
    map { (repeat each $0) } ?? (repeat nil as (each WrappedElement)?)
  }
}

Workaround:

extension Optional {
  func unzip<each WrappedElement>() -> (repeat (each WrappedElement)?)
  where Wrapped == (repeat each WrappedElement) {
    switch self {
    case let wrapped?: (repeat each wrapped)
    case nil: (repeat nil as (each WrappedElement)?)
    }
  }
}

* Ha! Maybe stick with that global function until post-beta season, when there might be some time for people to work on this… :slightly_smiling_face:‍↔

let two: Optional = (1, 2)
unzip(two)
two.unzip()

let one: Optional = 1
unzip(one)
one.unzip() // runtime crash
2 Likes