[Pitch] Variadic Sequences

Swift could make variadic parameter expressions more powerful by allowing repeated sequences of parameters in method definitions.

Here's a variadic function definition and call that is valid in Swift 5.7 syntax:

func myFunction(_ value: (name: String, age: Int)..., and otherName: String) {}

myFunction((name: "Ada", age: 26), (name: "Bob", age: 21), and: "Carl")

However, I believe the same definition/call should have the ability to look like this:

func myFunction(name: String..., age: ...Int, and anotherParameter: String) {}

myFunction(name: "Ada", age: 26, name: "Bob", age: 21, and: "Carl")

This could be a new language feature called a variadic sequence where any number of sequenced arguments can be included in a method definition. Note the new ...Int syntax: this represents the end of a variadic sequence. This feature gives developers a friendlier experience when writing method calls.

As for obtaining the variadic sequence within a function implementation, the dollar-sign syntax may be used. Alternatively, each varaidic sequence element can be obtained as an array of tuples via argument name. Finally, variadic sequence can be iterated through using a for in loop [1].

func myFunction(a: String..., b: ...Int) {
  let allSequences: [(a: String, b: Int)] = $0
  let allValuesOfA: [String] = a
  let allValuesOfB: [Int] = b

  for myA, myB in $0 {
    let aValue: String = myA
    let bValue: Int = myB
  }
}

Proposed Specifics

  • Every variadic sequence begins and ends with open and close parameters. Open parameters use the Type... syntax. Close parameters use a new ...Type syntax.
func myFunction(_ open: String..., _ close: ...String) {}
  • Variadic sequences can contain middle parameters. The type of a middle parameter uses normal syntax:
// Variadic sequence with 3 elements
func myFunction(_ open: String..., _ middle: String, _ close: ...String) {}

// Variadic sequence with 4 elements
func myFunction(_ a: String..., _ b: String, _ c: String, _ d: ...String) {}
  • Variadic sequences can use any types desired.
func myFunction<T>(_ a: String..., _ b: Double, _ c: ...T, _ d: Int) {}

myFunction("Swift", 5.7, objectA, "Objective-C", 2.0, objectB, 1234)
  • Calls to functions with variadic sequence parameters can include as many repetitions as desired:
myFunction("A", 1, "B", 2, "C", 3, "D", 4) {}
  • Varadic sequence elements may be named [2]:
func myFunction<A, B>(property path: KeyPath<A, B>..., _ middle: Comparison, value: ...B) {}

myFunction(property: \.name, .equals, value: "Ada", property: \.age, .greaterThan, value: 21)
  • Multiple variadic sequences of varying lengths may be present in a method definition. In this case, each respective sequence is obtained using $0, $1, $2, etc.:
func myFunction(_ a: String..., _ b: ...Int, _ c: Double..., _d: Date, _ e: ...Int) {
  let abValues: [(String, Int)] = $0
  let cdeValues: [(Double, Date, Int)] = $1
}
  • Variadic sequences can be used alongside regular parameters, and existing variadic arguments:
func myFunction(_ a: String..., _ b: ...Int, _c: Double, _ d: Int...)
myFunction("Ada", 26, "Bob", 21, 5.7, 1, 2, 3, 4)

Restrictions

  • One may not create a varadic argument that is itself a variadic sequence.
  • Variadic sequences may not contain any varadic elements.
// Two invalid syntaxes
func myFunction(_ invalidOpen: (String...)..., _ close: ...String)
func myFunction(_ open: String..., _ middle: String, _ invalidMiddle: String..., _close: ...String)

Remaining Hiccups (ideas/help needed!)

  • I believe there should be some way to name variadic sequences. But doing so would require developers to learn another syntax rule (in addition to the feature's basic ...Close syntax). Moreover, I cannot imagine of a syntax that maintains Swift-like appearance. Here is my thought process:
// We aim to give this variadic sequence some name:
func f(_ a: String..., _ b: ...Int) {}
f("A", 1)

// Here's an attempted syntax:
func f(vals: _ a: String..., _ b: ...Int) {}
f(vals: "A", 1)
// The issue here is that the developer must learn the 'double: name:' syntax.
// Besides, this syntax isn't very developer-friendly.

// If we try to name sequence element parameters...
func f(vals: a: String..., b: ...Int) {}
f(vals: a: "A", b: 1)
// Again, it looks a bit messy.
  • I believe the dollar-sign syntax $0 (see [1]) used to obtain the variadic sequence array within a function's implementation may cause issues with Swift's syntax. This syntax is usually only seen within closures. I believe this issue could improved by either:

    • Using a special keyword to replace $0.
    • Using a variadic sequence name as mentioned in the first hiccup.
  • The value of the $0 variadic sequence ([(TypeA, ..., TypeB)]), an array of tuples, may have a naming clash issue. Would the argument name or parameter label be used in the tuple? Consider:

func f(a valA: String..., b valB: ...Int) {
  // Is $0 of type (a: String, b: Int) or (valA: String, valB: Int) ?
  let valueA: String = $0.a // Clash!
  let valueA: String = $0.valA // Clash!
}
  • The purpose of the following bullet point asks about the parity with Swift's existing Variadic Argument feature:
func f(a: Int...) {}
f(a: 1, 2, 3, 4) // The name label 'a' appears once.

If a variadic sequence can have named elements (see [2]), should only the first sequence of elements passed to a function call use names? Here is the question illustrated:

func f(name: String..., age: ...Int) {}

f(name: "Ada", age: 26, name: "Bob", age: 21) // Names always present
f(name: "Ada", age: 26, "Bob", 21) // Names on first sequence only

Closing

Please let me know if you have any ideas for my list of hiccups. Do you think this feature has the potential to be a part of the Swift Evolution process? Do you have any thoughts or improvements? I will be monitoring the comment thread! If this gets traction, I'll build it in a Swift fork.

Thanks,
Ben

1 Like