Is it possible to `repeat` statements that do not reference a parameter pack?

Consider this example:

struct IdentifiedValue<T> {

    typealias ID = UUID

    var id: ID
    var value: T
}

class ValueStore {
    func add<T>(_ value: T) -> IdentifiedValue<T>.ID {
        let id = nextID()
        let identifiedValue = IdentifiedValue(id: id, value: value)
        // Do something with identifiedValue…
        return id
    }
}

What this add(_:) method does is that it gets a UUID from somewhere, passes it into the IdentifiedValue initializer and then return that ID.

What is the best way to turn this add(_:) into a method that takes multiple parameters? I can’t just add the each and repeat keywords because then the return statement isn’t correct (obviously):

func add<each T>(_ value: repeat each T) -> (repeat IdentifiedValue<each T>.ID) {
    let id = nextID()
    let identifiedValue = repeat IdentifiedValue(id: id, value: each value)
    // Do something with identifiedValue…
    return id // error
}

The only way I found was to get everything into a single line so the repeat and each keywords act on the whole line. This means adding a separate method that does the thing for a single value and calling that.

At first, I tried adding both of the add(_:) variants and implementing the one that takes a pack by calling the one that takes a single value, but then the compiler says it doesn’t know which version of add(_:) to call. I could rename one of the two but I ended up using an inner function (where it isn’t confused, I guess due to shadowing) like this:

func add<each T>(_ value: repeat each T) -> (repeat IdentifiedValue<each T>.ID) {

    func add<T>(_ value: T) -> IdentifiedValue<T>.ID {
        let id = nextID()
        let identifiedValue = IdentifiedValue(id: id, value: value)
        // Do something with identifiedValue…
        return id
    }

    return (repeat self.add(each value))
}

So my question is: Is there way to write this without a second function?

Replies

Is there way to write this without a second function?

Probably not, but it’s hard to say for sure without a little more context.

I tried assembling your code snippets into what I think is a consistent representation of what you ended up with. However, that code wouldn’t compile for various reasons. I eventually tweaked it to look like this:

import Foundation

struct IdentifiedValue<T> {

    typealias ID = UUID

    var id: ID
    var value: T
}

class ValueStore {

    func add<each T>(_ value: repeat each T) -> (repeat IdentifiedValue<each T>.ID) {
        func wrap<T2>(_ value: T2) -> IdentifiedValue<T2>.ID {
            let id = nextID()
            let identifiedValue = IdentifiedValue(id: id, value: value)
            // Do something with identifiedValue…
            return id
        }
        return (repeat wrap(each value))
    }
}

func nextID() -> UUID {
    fatalError()
}

Is that a reasonable match for your current goal?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • (I removed this comment and posted it as a reply below for truncation reasons)

  • (I removed this comment and posted it as a reply below for code formatting reasons)

Add a Comment

Oh, you’re right, my code doesn’t compile due to the duplicate generic parameter name T. Sorry about that. And yes, what you wrote is pretty much where I intended to end up. Weirdly enough, it doesn’t compile, though, and all I get is a nondescript “Command SwiftCompile failed with a nonzero exit code” 😕 In my actual code where I stumbled across this, it does compile, though. I tried but I couldn’t manage to get my sample code (or yours) to compile…

FWIW, here’s my actual code:

/// Adds a side effect that is performed whenever any of the tweaks’ value changes.
@discardableResult
public static func addSideEffect<each Value>(to tweak: repeat Tweak<each Value>, sideEffect: @escaping () -> Void) -> (repeat Tweak<each Value>.SideEffectID) {

    func add<V>(to tweak: Tweak<V>) -> CustomSideEffect.ID {
        let sideEffect = CustomSideEffect(action: sideEffect)
        tweak.tweaks.store.sideEffectPerformer.add(.custom(sideEffect), for: tweak.id)
        return sideEffect.id
    }

    return (repeat add(to: each tweak))
}

Weirdly enough, it doesn’t compile, though

Weird. I actually ran it through the compiler before posting it, using Xcode 15.0b1 in a newly created iOS app test project.

all I get is a nondescript “Command SwiftCompile failed with a nonzero exit code”

Yeah, I saw a bunch of those myself )-: This stuff is still quite bleeding edge.

Anyway, coming back to the big picture, right now you have to jump through these hoops because there are serious limits on how you can use pack values. My understanding is that the long-term goal is to support some sort of for loop (see the original pitch on Swift Evolution) but we’re not quite there yet.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Interesting, copying your code into a new project also works for me 🤷‍♂️

Thanks for all your input! I guess time will tell and we’ll see whether what I’m trying to do will be possible at some point. As long as there’s the single line workaround using a separate function, it’s all right.