Any way to express this concept with parameter packs, without wrapping in a tuple?

Suppose that I'm building a "type stack". I want to have a list of types, and be able to pop a type off of the stack. The queue must have at least one element in it, and I want to be able to call specific functions on the instance at the head of the queue.

Here is my best attempt to implement this with a struct that explicitly has a variadic generic:

struct TypeStack<each Element, Head> {

    func pop<each T, U>() -> TypeQueue<repeat each T, U> where (repeat each T, U, Head) == (repeat each Element, Head) {
        fatalError()
    }
}

This will not compile, because I've created a "same type" requirement.

I also can't just do pop() -> TypeStack<repeat each Element>, because there is no guarantee that each Element has a count >= 1.

But it's easy to do with tuples:

struct TypeStack<Contents> {

    func pop<each T, U, Head>() -> TypeQueue<(repeat each T, U)> where Contents == (repeat each T, U, Head) {
        fatalError()
    }
}

This is basically the same declaration, the "same type requirement" error seems like a pretty arbitrary restriction.

Anyway, I'm wondering if there is a way to implement pop using something like the first declaration?

withUnsafeTemporaryAllocation is what I'd try for something like this. There are a lot of limitations with this approach, but it works if you know what you're doing (and have the time and patience getting it to work nicely).

You cannot manipulate parameter packs at runtime like an array. How is your data stored for the TypeStack and TypeQueue? Maybe, assuming you can use a Swift 6.2+ toolchain, you can try an InlineArray with extensions that manipulate the storage like you want, but even that has its own limitations.

This question isn't about the implementation of pop(); I'm interested in ways to get something like the first struct + method declaration to compile. I want to be able to write TypeStack<X, Y> instead of TypeStack<(X, Y)> and not need massive where clauses on every single method declaration that require Elements to be a tuple.

Internally I would probably store it as a tuple regardless of the external impl, that works fine. My actual use case isn't really a stack though; that's just the simplest way to describe what I want.

I am not aware of a solution other than using tuples that behaves the you want. It is worth noting that the solution you want would be possible with pack destructuring, which is listed as a future direction in the parameter packs proposal. I am also unaware of any development relating to parameter pack improvements or features, so I guess you'd have to implement pack destructuring yourself or try using other concepts.

Edit 18h later: Looks like there is growing interest in pack destructuring as someone recently pitched the feature here on the forums.

Could you elaborate more on those massive where clauses?

As others mentioned, parameter pack destructuring is not currently possible. You could, however, use nested tuples to achieve what you want, if it is still applicable to your use case.

struct TypeStack<Elements, Head> {
    let content: (Elements, Head)
    
    private init(content: (Elements, Head)) {
        self.content = content
    }
    
    func pop<T, U>() -> TypeStack<T, U> where Elements == (T, U) {
        .init(content: content.0)
    }
}

A result builder can alleviate the burden of typing those nested tuples:

extension TypeStack {
    init(@TypeStackBuilder _ body: () -> (Elements, Head)) {
        content = body()
    }
}

@resultBuilder struct TypeStackBuilder {
    static func buildPartialBlock<U>(first: U) -> U {
        first
    }
    
    static func buildPartialBlock<U, V>(accumulated: U, next: V) -> (U, V) {
        (accumulated, next)
    }
}

let stack = TypeStack { 1; 2.0; "three"; [4] }
2 Likes