Parameter packs: how do you apply closures to arguments?

I'm still in the "parameter packs look like parenthesis soup" stage (tuples or not in input positions is especially confusing right now). I've left them all out. Where do they go?

@inlinable public func allSatisfy<each Element>(
  _ element: repeat each Element,
  predicate: repeat each Element -> Bool
) -> Bool {
  guard repeat each predicate(element) else { return false }
  return true
}
@inlinable public func allSatisfy<each Element>(
  _ element: repeat each Element,
  predicate: (repeat each Element) -> Bool
) -> Bool {
  guard predicate(repeat each element) else { return false }
  return true
}

Unless you're trying to pass in one predicate per element, in which case you would write this signature:

@inlinable public func allSatisfy<each Element>(
  _ element: repeat each Element,
  predicate: repeat (each Element) -> Bool
) -> Bool

The body becomes a little more complicated because you need to fold each call to predicate into a single result. Is that what you're trying to accomplish, or does the predicate accept all input types?

2 Likes

Is this what you're looking for?

func isSome<each Wrapped>() -> (repeat ((each Wrapped)?) -> Bool) {
  func isSome<W>(_ optional: W?) -> Bool {
    optional != nil
  }

  return (repeat isSome as ((each Wrapped)?) -> Bool)
}

The as coercion in the return statement is only necessary because it specifies that the repeat should iterate over each Wrapped, e.g. you want to repeat the isSome predicate for each Wrapped type in the parameter pack. There are other ways to express this, but the as coercion shows you that each function value has a different type.

1 Like

Yeah, I think the problem here is that you want one function value that's generic over its input type. Instead, this pack expansion:

predicate: repeat (each Element) -> Bool

means that you're passing in separate function values - one for each Element type in the argument pack. So, in your "meta-predicate" here:

func isSome<each Wrapped>() -> (repeat ((each Wrapped)?) -> Bool) {
  ...
}

You're not returning a single generic function value; you're returning N function values stored in a tuple. The reason why you're getting an error here:

    predicate: isSome  // Command SwiftCompile failed with a nonzero exit code

is because you're trying to pass a tuple containing these function values, but the allSatisfy function actually expects separate arguments. Obviously this should emit an error instead of crashing, but this appears to be fixed on main (I got a compiler error locally for this code)

Allowing a general tuple expansion operation for concrete tuples would fix this issue, or you can write it today by passing in separate closures:


public func allSatisfy<each Element>(
  _ element: repeat each Element,
  predicate: repeat (each Element) -> Bool
) -> Bool {
  var allSatisfy = true
  repeat (each predicate)(each element) ? () : (allSatisfy = false)
  return allSatisfy
}


print(allSatisfy(1, "hello", nil as Bool?, predicate: { $0 != nil }, { $0 != nil }, { $0 != nil }))

This compiles locally for me, and it prints false. It's obviously not good that you have to repeat the predicate N times when really you want the same code for each element in the pack. Instead, I think the best way to express this right now is with a functor-style object, where you actually can write the predicate as a generic function:


func allSatisfy<each Element, Predicate: Functor>  (
  _ element: repeat each Element,
  predicate: Predicate
) -> Bool where Predicate.Result == Bool {
  var allSatisfy = true
  repeat predicate.evaluate(each element) ? () : (allSatisfy = false)
  return allSatisfy
}

protocol Functor {
  associatedtype Result
  func evaluate<T>(_: T) -> Result
}

protocol OptionalProtocol {
  var isNil: Bool { get }
}

extension Optional: OptionalProtocol {
  var isNil: Bool {
    switch self {
    case .some: false
    case .none: true
    }
  }
}

struct IsSome: Functor {
  func evaluate<T>(_ value: T) -> Bool {
    switch value {
    case let optional as any OptionalProtocol: optional.isNil
    default: false
    }
  }
}


print(allSatisfy(1, "hello", nil as Bool?, predicate: IsSome())) // prints 'false'

This forces you to write all of your predicate code in a generic context, so e.g. checking for nil becomes harder, but the same would be true if Swift supported generic closures.

1 Like

And yes, there absolutely should be a better way to express pack folds!

1 Like