Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Enhanced Variadic Parameters

Enhanced Variadic Parameters

Introduction

This proposal enhances variadic parameters by introducing support for user-defined types, labeled variadic parameters, and a choice at the call site of whether to provide a variadic argument list or a collection value.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

Variadic parameters are a nice feature in Swift. They allow us to elide array brackets at a call site. Unfortunately this elision comes with a cost: even though variadics formally take an Array one cannot be provided at a call site. This often results in an overload set to support direct arrays as well:

func compute(ints: Int...) -> Int {
   return compute(ints: ints)
}
func compute(ints: [Int]) -> Int { ... }

Further, Array is priveleged as the only type that is supported by variadic parameter syntax. This is at odds with Swift's otherwise pervasive support for user-defined types in its literal syntax.

Proposed solution

We should reimagine variadic parameters as simple syntactic sugar for optionally eliding collection literal brackets at the call site.

Syntax

Swift magically turns the current T... type syntax into [T] as the type used in the body of a function. Having the Array assumption baked directly into this syntax means it will not work with other types.

In order to support types beyond Array the variadic parameter modifier is used:

func compute(ints: variadic [Int]) -> Int {...} 

// callable in any of these ways:
compute(ints: 42, 43, 44)
compute(ints: [42, 43, 44])
compute(ints: arrayOfInt)

ExpressibleByArrayLiteral

All ExpressibleByArrayLiteral types are supported. The compiler will use the type's init(arrayLiteral:) initializer to create a value from variadic arguments provided at the call site.

func compute(ints: variadic Set<Int>) -> Int {...} 

// callable in any of these ways:
compute(ints: 42, 43, 44)
compute(ints: [42, 43, 44])
compute(ints: setOfInt)

ExpressibleByDictionaryLiteral

ExpressibleByDictionaryLiteral types are also supported with a variadic list of labeled arguments. The labels in this list are key literals and the arguments are values in a dictionary literal.

In order to use the type's init(dictionaryLiteral:) initializer arguemnt labels must be turned into values of type Key. This proposal includes two ways to do that.

ExpressibleByStringLiteral keys

When the key is ExpressibleByStringLiteral, the argument label can be passed to init(stringLiteral:) to create a value of type Key.

func compute(pairs: variadic [String: Int]) -> Int { ... }

// callable in any of these ways:
compute(first: 42, second: 43, third: 44)
compute(pairs: ["first": 42, "second": 43, "third": 44])
compute(pairs: dictionaryFromStringToInt)

Notice that this sugar allows not only the dictionary literal brackets to be ellided, but the string quotes and the eternal pairs label as well. pairs is only used when a collection value is passed directly and variadic syntax is not used.

This feature is not limited to Dictionary:

// assume OrderedDictionary: ExpressibleByDictionaryLiteral
func compute(pairs: variadic OrderedDictionary<String, Int>) -> Int { ... }

// callable in any of these ways:
compute(first: 42, second: 43, third: 44)
compute(pairs: ["first": 42, "second": 43, "third": 44])
compute(pairs: orderedDictionaryFromStringToInt)

Enum keys

When the key is of an enum type that does not have any associated values keys can be required to match the identifiers of its cases.

enum Foo { case foo, bar, baz }
func compute(pairs: variadic [Foo: Int]) -> Int { ... }

// callable in any of these ways:
compute(foo: 42, bar: 43, baz: 44)
compute(pairs: [.foo: 42, .bar: 43, .baz: 44])
compute(pairs: dictionaryFromFooToInt)

Notice that this sugar allows not only the dictionary literal brackets to be ellided, but the dot prefix on the enum case identifier and the eternal pairs label as well. pairs is only used when a collection value is passed directly and variadic syntax is not used.

This feature is not limited to Dictionary:

enum Foo { case foo, bar, baz }
func compute(pairs: variadic OrderedDictionary<Foo, Int>) -> Int { ... }

// callable in any of these ways:
compute(foo: 42, bar: 43, baz: 44)
compute(pairs: [.foo: 42, .bar: 43, .baz: 44])
compute(pairs: orderedDictionaryFromFooToInt)

Detailed design

TODO

Source compatibility

TODO

Effect on ABI stability

TODO

Effect on API resilience

TODO

Alternatives considered

TODO

Future directions

TODO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.