[Proposal Draft] parameter forwarding


(Matthew Johnson) #1

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.


(Félix Cloutier) #2

I was okay with memberwise initializers but this pushes me beyond my comfort zone.

I'm not sold on the usefulness of the feature. Memberwise initializers save you from typing out the init parameters and assignments to each field. Argument forwarding saves you from spelling out the parameters *more than once* (because you still need to type them out for the receiving function) and from *one call*. While I've been annoyed at initializers, I don't think I've ever been particularly annoyed at forwarding functions.

I'll let the parser guys say if it's a lot of work to implement or not, but if I allow myself to speak outside of my expertise, I imagine that it's gonna be a lot more work than memberwise initializers because this requires inspecting the function body to figure out its parameters.

At this point, I feel that a competent macro system is a better investment than adding distinct bits of automation wherever there appears to be repetition.

Félix

···

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution <swift-evolution@swift.org> a écrit :

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #3

This approach is extremely problematic and unlikely to be accepted.

Swift is carefully designed so that the compiler can type check the interface to a function without type checking its body. This is important for compiler scalability to large projects, IDE tooling, and also because otherwise you can run into circular dependencies to compile the code.

Another major problem with this approach is that it only works if “foo” is unambiguous.

-Chris

···

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}


(Matthew Johnson) #4

I was okay with memberwise initializers but this pushes me beyond my comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to features in other languages that are generally considered to be quite useful.

For example, most dynamic languages have the ability to pack arguments. There has been discussion of adding tuple packing and unpacking to Swift but it wouldn’t offer everything it does in dynamic languages (as far as I can tell).

Swift is statically typed so we would have to specify a type for the tuple to pack / unpack. This means it must be a fixed list of arguments. This is not the case in dynamic languages, where whatever arguments the caller provides are forwarded.

Also, because the packed tuple parameter is declared manually, there would not be a way to forward a default argument value for specific members of the tuple as far as I can see (I think a default would need to be specified for the entire tuple, not just specific members). Even if there were a way to specify a value for specific members, I don’t believe it would be possible to “forward” the default value specified by the receiving function, which is actually what is desired. In dynamic languages, callers can just provide a subset of the tuple arguments and the receiving function detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an arbitrary set of arguments to a receiving function. This feature of C++ relies on the fact that the body of a template is not checked until it is expanded. This allows the caller of the forwarding function to supply any set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of forwarding fits the language as the body of a generic function is checked on its own. I don’t believe there would be a way to specify constraints that would allow the arguments to be used to call the forwarding function (I may be wrong about that if a new kind of constraint was introduced to support this in the future).

The forwarding mechanism in this proposal supports a couple of things that I think will be quite useful which are not possible under the examples of tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole packed tuple
2. Forwarding default parameter values from the forwardee function for said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to suppress forwarding of specific parameters where the callee provides a default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers save you from typing out the init parameters and assignments to each field. Argument forwarding saves you from spelling out the parameters *more than once* (because you still need to type them out for the receiving function) and from *one call*. While I've been annoyed at initializers, I don't think I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save explicit declaration of parameters as well as a single action with the provided argument.

More importantly, forwarding is a general purpose feature that when combined with partial initializers and property lists can support much more expressive memberwise initialization than contained in the initial proposal. There was quite a bit of discussion about both the limitations of the memberwise initialization proposal as well as the specificity of it to exactly one use case (memberwise initialization). Forwarding plays a role in removing the limitations while building on a more general foundation.

Here’s an example that takes advantage of the combined power of the three proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let` properties
5. Allows more-private properties to be exposed by more-public initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise initializer corresponding to the properties specified (it also gives you a computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming partial initializers and omitting the `args` tuple property that would be synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”) {
    self.x = x
    self.y = y
    self.name = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use cases for memberwise initialization while remaining concise and arguably more clear (because the property list explicitly states which members participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including concise forwarding of parameters to a super or member initializer, or forwarding from a convenience initializer that just needs to provide a few direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise initialization logic
3. Computed tuple properties for each propertylist. (and possibly additional memberwise features in the future if we identify any that would also be generally useful)

In my opinion this is a huge win for both initialization as well as other parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not, but if I allow myself to speak outside of my expertise, I imagine that it's gonna be a lot more work than memberwise initializers because this requires inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly provided arguments with the parameter list of any callee overloads that are in scope and determining whether:

1. There are no overloads for which the provided arguments could be part of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default argument values, and possibly (but very desirable IMO) a subset of parameters there is no way to avoid this logic. I am not an expert at the implementation of such features, but I don’t think it is excessively complex next to other logic implemented in the compiler.

At this point, I feel that a competent macro system is a better investment than adding distinct bits of automation wherever there appears to be repetition.

I agree that a macro system would be great, but it is explicitly not in scope for Swift 3. It would also not be capable of implementing parameter forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

···

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #5

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

This approach is extremely problematic and unlikely to be accepted.

Ok, I won’t pursue it further in that case. Thanks for calling that out before I spent any further time on it.

Swift is carefully designed so that the compiler can type check the interface to a function without type checking its body. This is important for compiler scalability to large projects, IDE tooling, and also because otherwise you can run into circular dependencies to compile the code.

Good to know.

Another major problem with this approach is that it only works if “foo” is unambiguous.

I addressed the fact that the call to foo must be unambiguous further down in the proposal. Did I miss something important? (Not that it matters if this approach is a dead end)

Do you have any ideas for a better direction that could handle forwarding of default parameter values and / or a subset of parameters? Or is tuple packing and unpacking the best we can hope for?

Matthew

···

On Jan 11, 2016, at 7:53 PM, Chris Lattner <clattner@apple.com> wrote:
On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(TJ Usiyan) #6

I can't get behind this feature. Beside the lack of payoff for added
syntax, the biggest problem is that this is mostly handled by default
values. If we had (once we get?) constant expressions, I imagine that we
might even be able to reference other parameters in default value method
calls–which seems like a more generally useful fix for the issue.

TJ

···

On Mon, Jan 11, 2016 at 10:29 AM, Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

I was okay with memberwise initializers but this pushes me beyond my
comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to
features in other languages that are generally considered to be quite
useful.

For example, most dynamic languages have the ability to pack arguments.
There has been discussion of adding tuple packing and unpacking to Swift
but it wouldn’t offer everything it does in dynamic languages (as far as I
can tell).

Swift is statically typed so we would have to specify a type for the tuple
to pack / unpack. This means it must be a fixed list of arguments. This
is not the case in dynamic languages, where whatever arguments the caller
provides are forwarded.

Also, because the packed tuple parameter is declared manually, there would
not be a way to forward a default argument value for specific members of
the tuple as far as I can see (I think a default would need to be specified
for the entire tuple, not just specific members). Even if there were a way
to specify a value for specific members, I don’t believe it would be
possible to “forward” the default value specified by the receiving
function, which is actually what is desired. In dynamic languages, callers
can just provide a subset of the tuple arguments and the receiving function
detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an
arbitrary set of arguments to a receiving function. This feature of C++
relies on the fact that the body of a template is not checked until it is
expanded. This allows the caller of the forwarding function to supply any
set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of
forwarding fits the language as the body of a generic function is checked
on its own. I don’t believe there would be a way to specify constraints
that would allow the arguments to be used to call the forwarding function
(I may be wrong about that if a new kind of constraint was introduced to
support this in the future).

The forwarding mechanism in this proposal supports a couple of things that
I think will be quite useful which are not possible under the examples of
tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole
packed tuple
2. Forwarding default parameter values from the forwardee function for
said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to suppress
forwarding of specific parameters where the callee provides a default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers
save you from typing out the init parameters and assignments to each field.
Argument forwarding saves you from spelling out the parameters *more than
once* (because you still need to type them out for the receiving function)
and from *one call*. While I've been annoyed at initializers, I don't think
I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save
explicit declaration of parameters as well as a single action with the
provided argument.

More importantly, forwarding is a general purpose feature that when
combined with partial initializers and property lists can support much more
expressive memberwise initialization than contained in the initial
proposal. There was quite a bit of discussion about both the limitations
of the memberwise initialization proposal as well as the specificity of it
to exactly one use case (memberwise initialization). Forwarding plays a
role in removing the limitations while building on a more general
foundation.

Here’s an example that takes advantage of the combined power of the three
proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init
proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let`
properties
5. Allows more-private properties to be exposed by more-public initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise
initializer corresponding to the properties specified (it also gives you a
computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward
arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming
partial initializers and omitting the `args` tuple property that would be
synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”) {
    self.x = x
    self.y = y
    self.name = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use cases
for memberwise initialization while remaining concise and arguably more
clear (because the property list explicitly states which members
participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including
concise forwarding of parameters to a super or member initializer, or
forwarding from a convenience initializer that just needs to provide a few
direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise
initialization logic
3. Computed tuple properties for each propertylist. (and possibly
additional memberwise features in the future if we identify any that would
also be generally useful)

In my opinion this is a huge win for both initialization as well as other
parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not,
but if I allow myself to speak outside of my expertise, I imagine that it's
gonna be a lot more work than memberwise initializers because this requires
inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly provided
arguments with the parameter list of any callee overloads that are in scope
and determining whether:

1. There are no overloads for which the provided arguments could be part
of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be
part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments
could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default argument
values, and possibly (but very desirable IMO) a subset of parameters there
is no way to avoid this logic. I am not an expert at the implementation of
such features, but I don’t think it is excessively complex next to other
logic implemented in the compiler.

At this point, I feel that a competent macro system is a better investment
than adding distinct bits of automation wherever there appears to be
repetition.

I agree that a macro system would be great, but it is explicitly not in
scope for Swift 3. It would also not be capable of implementing parameter
forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> a écrit :

I have always considered the Flexible Memberwise Initialization proposal
to be just a first step (as evidenced by the many future enhancements it
discussed). Its review has inspired new ideas and helped to shape my
vision of the best long-term solution. My final thoughts about the review
can be found here:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals
describing general features that can work together to form a complete
solution.

The proposal drafts can be found at the following links:

* *Parameter forwarding:*
https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* *Partial initializers:*
https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* *Property lists:*
https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

   - Proposal: SE-NNNN
   <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
   - Author(s): Matthew Johnson <https://github.com/anandabits>
   - Status: *Awaiting review*
   - Review manager: TBD

Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding
<https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the
purpose of forwarding the provided arguments to another function. This
results in reduntant parameter specifications that make code less clear and
less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

   - Convenience initializers that foward parameters directly to a
   designated initializer
   - Designated initializers that foward parameters directly to a super
   initializer
   - Designated initializers that foward parameters directly to a member
   initializer, perhaps in a composition-based design
   - If the partial initilaizer proposal is accepted, designated
   initializers that forward parameters to one or more partial initializers

NOTE: I haven’t had time to think too much aboue use cases beyond
initialization. Please share examples and I will add them to this proposal.
Proposed solution

The proposed solution is to introduce an automatic parameter forwarding
mechansim. It allows users to provide direct arguments for some parameters
while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Some things to note about the syntax:

   1. ...fooParams is a placeholder introduced with ... and followed by
   an identifier.
   2. In the signature it can be placed anywhere in the parameter list.
   3. At the call site, it must appear at the end of the argument list.
   4. The placeholder matches the parameters not directly provided
   including their external label and default value if those exist.
   5. Parameters corresponding to the matched parameters are synthesized
   by the compiler where the placeholder exists in the parameter list,
   including the default argument if one exists.
   6. The identifier portion of the placeholder may be omitted if only
   one set of forwarded parameters exist within the function.

Additional details will be introduced with a corresponding example.
Omitting the placeholder identifier

The above example can be written more concisely by omitting the
placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...) {
    foo(i: 32, ...)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

NOTE: If the community feels strongly that the identifier should be
required I am willing to do so.
Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of
parameters:

func foo(i i: Int, s: String, f: Float = 42) { }func foo2(d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Direct arguments

Any direct arguments provided in the forwarding call must follow the usual
argument ordering rules, with the only exception being that it is allowed
to omit some arguments that would normally be required. When the compiler
performs forwarding it will insert forwarded arguments in the correct
location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}
// user writes:func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}
// compiler synthesizes:func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}

Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as
long as the signature of the matched parameters matches exactly, including
any default values.

func foo(i i: Int, s: String, d: Double = 43) { }func bar(i i: Int, s: String, d: Double = 43) { }
// user writes:func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}
// compiler synthesizes: func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}

NOTE: This provision might be controversial. If the community doesn’t like
it or the implementation is too complex I will remove it.
Unambiguous call

When forwarding parameters to a function that is overloaded the caller
must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)

    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}
// compiler synthesizes: func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}

Default values

When forwarding to a function that accepts default values it is possible
to explicitly request the default value. This allows for disambiguation and
also allows the forwarding function to suppress a defaulted parameter from
participating in forwarding without needing to supply a specific value. The
default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}

It is also possible to explicitly request all defaults at once using
default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(default..., ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}

NOTE: The actual implementation of default arguments looks somewhat
different. These examples are intended to communicate the behavior, not the
exact details of implementation.
Generic parameters

If the types of any matched parameters reference any generic type
parameters of the forwardee the generic type parameters must also be
forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(...fooParams)
}
// compiler synthesizes:func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}

If a generic parameter is referenced in a constraint that also references
a generic parameter that will not be forwarded the constraint is resolved
to a concrete type when possible. This may not be possible in all cases.
When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }
// user writes:func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}
// compiler synthesizes:func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}

Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal
names that do not conflict with the internal names of any manually declared
parameters. This applies to both generic type parameter names as well as
value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}
// compiler synthesizes:func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}

Detailed design

TODO but should fall out pretty clearly from the proposed solution
Impact on existing code

This is a strictly additive change. It has no impact on existing code.
Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and
any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider
anything reasonably clear and concise to be acceptable.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #7

I was okay with memberwise initializers but this pushes me beyond my comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to features in other languages that are generally considered to be quite useful.

/* snip */

I don't find it similar to how dynamic languages do parameter packing, and not useful in the same conditions. In Python, you'll use parameter packing and unpacking mostly with functions that receive variable parameters. Swift's variadic functions don't work like that. It's strictly less powerful and less needed than C++'s perfect forwarding because Swift doesn't have lvalue (or rvalue) references. Besides, perfect forwarding is mostly used when you don't know the call target, and the parameter packing proposal requires you to know it.

The fact that there are forwarding solutions in other languages doesn't mean that Swift has the problems that these solutions are trying to address.

I'm not sold on the usefulness of the feature. Memberwise initializers save you from typing out the init parameters and assignments to each field. Argument forwarding saves you from spelling out the parameters *more than once* (because you still need to type them out for the receiving function) and from *one call*. While I've been annoyed at initializers, I don't think I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save explicit declaration of parameters as well as a single action with the provided argument.

Here's a 4-field example for memberwise initializers:

struct Foo {
  var bar: Int
  var baz: Int
  var frob: Int
  var nicate: Int
}

it's either gonna be "memberwise init(...) {}" (23 chars) or:

  init(bar: Int, baz: Int, frob: Int, nicate: Int) {
    self.bar = bar
    self.baz = baz
    self.frob = frob
    self.nicate = nicate
  }

120 characters excluding tabs, and with very short insignificant names. That's like six times less code for the same thing.

Let's have a 4-parameter method:

class Foo {
  func foo(bar: Int, baz: Int, frob: Int, nicate: Int) { /* snip */ }
}

Now this is either going to be "func bar(...fooPack) { foo(bar: 1, ...fooPack) }" (48 chars) or "func bar(baz: Int, frob: Int, nicate: Int) { foo(bar: 1, baz: baz, frob: frob, nicate: nicate) }" (96 chars).

More importantly, forwarding is a general purpose feature that when combined with partial initializers and property lists can support much more expressive memberwise initialization than contained in the initial proposal. There was quite a bit of discussion about both the limitations of the memberwise initialization proposal as well as the specificity of it to exactly one use case (memberwise initialization). Forwarding plays a role in removing the limitations while building on a more general foundation.

I disagree with the critics and I don't support these other proposals. In my opinion, the objections you're referring to were theoretical-style "this doesn't solve the General Problem". The vast majority of users don't need the General Problem to be solved, and if a specific solution gets us 95% of the way there with little disturbance, it's probably best to take it and leave behind the major changes required for the remaining 5%.

The question here is whether memberwise initialization gets us 95% of the way there. I think it does, and I'm gonna need real code and user feedback to change my mind. This is unlikely to happen before memberwise initializers ship.

I really think that we should wait to see how memberwise initialization plays out before laying out plans to extend it.

···

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

At this point, I feel that a competent macro system is a better investment than adding distinct bits of automation wherever there appears to be repetition.

I agree that a macro system would be great, but it is explicitly not in scope for Swift 3. It would also not be capable of implementing parameter forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #8

Or is tuple packing and unpacking the best we can hope for?

If we do go the tuple packing/unpacking route, I wonder if we can add syntax that gets the parameter and return types of a function:

  func fooWithLogging(*params: foo.Parameters) -> foo.Return {
    print("Calling foo with \(params)")
    
    let value = foo(*params)
    
    print(" Returned \(value)")
    return value
  }

(Here I'm using a Ruby/Perl 6-style splat, since we're leaning pretty heavily on `...` already.)

···

--
Brent Royal-Gordon
Architechies


(Chris Lattner) #9

Another major problem with this approach is that it only works if “foo” is unambiguous.

I addressed the fact that the call to foo must be unambiguous further down in the proposal. Did I miss something important? (Not that it matters if this approach is a dead end)

No, you have it covered. I know you’re interested in initializer forwarding in particular, and overloaded initializers are very common, that’s why I raised it as a potential issue.

Do you have any ideas for a better direction that could handle forwarding of default parameter values and / or a subset of parameters? Or is tuple packing and unpacking the best we can hope for?

There is no structural problems with mechanisms that take parameters and forward to other calls, e.g.:

func f(a : Int, b : Float, c : String) {
   bar(@@@ARGS@@@) // obviously horrible to get the point across :-)
}

Related but different: forwarding of varargs is also a common special case. People have wanted to do this for a long time, we just haven’t had cycles to implement it:

func f(a : Int, b : Float…) {
   g(a, …b) // unpack the vars array into a list of parameters.
}

If we did that, I could see generalizing it to an arbitrary tuple:

g(x, y, …sometuple, z)

where sometuple’s members got passed as arguments.

-Chris

···

On Jan 11, 2016, at 6:00 PM, Matthew Johnson <matthew@anandabits.com> wrote:


(Matthew Johnson) #10

I can't get behind this feature. Beside the lack of payoff for added syntax, the biggest problem is that this is mostly handled by default values. If we had (once we get?) constant expressions, I imagine that we might even be able to reference other parameters in default value method calls–which seems like a more generally useful fix for the issue.

How is this handled by default values? What you’re saying doesn’t make sense to me. Can you provide an example of what you have in mind?

Have you used a language with a similar feature? Did you not find it useful?

Matthew

···

On Jan 11, 2016, at 10:03 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

TJ

On Mon, Jan 11, 2016 at 10:29 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

I was okay with memberwise initializers but this pushes me beyond my comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to features in other languages that are generally considered to be quite useful.

For example, most dynamic languages have the ability to pack arguments. There has been discussion of adding tuple packing and unpacking to Swift but it wouldn’t offer everything it does in dynamic languages (as far as I can tell).

Swift is statically typed so we would have to specify a type for the tuple to pack / unpack. This means it must be a fixed list of arguments. This is not the case in dynamic languages, where whatever arguments the caller provides are forwarded.

Also, because the packed tuple parameter is declared manually, there would not be a way to forward a default argument value for specific members of the tuple as far as I can see (I think a default would need to be specified for the entire tuple, not just specific members). Even if there were a way to specify a value for specific members, I don’t believe it would be possible to “forward” the default value specified by the receiving function, which is actually what is desired. In dynamic languages, callers can just provide a subset of the tuple arguments and the receiving function detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an arbitrary set of arguments to a receiving function. This feature of C++ relies on the fact that the body of a template is not checked until it is expanded. This allows the caller of the forwarding function to supply any set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of forwarding fits the language as the body of a generic function is checked on its own. I don’t believe there would be a way to specify constraints that would allow the arguments to be used to call the forwarding function (I may be wrong about that if a new kind of constraint was introduced to support this in the future).

The forwarding mechanism in this proposal supports a couple of things that I think will be quite useful which are not possible under the examples of tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole packed tuple
2. Forwarding default parameter values from the forwardee function for said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to suppress forwarding of specific parameters where the callee provides a default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers save you from typing out the init parameters and assignments to each field. Argument forwarding saves you from spelling out the parameters *more than once* (because you still need to type them out for the receiving function) and from *one call*. While I've been annoyed at initializers, I don't think I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save explicit declaration of parameters as well as a single action with the provided argument.

More importantly, forwarding is a general purpose feature that when combined with partial initializers and property lists can support much more expressive memberwise initialization than contained in the initial proposal. There was quite a bit of discussion about both the limitations of the memberwise initialization proposal as well as the specificity of it to exactly one use case (memberwise initialization). Forwarding plays a role in removing the limitations while building on a more general foundation.

Here’s an example that takes advantage of the combined power of the three proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let` properties
5. Allows more-private properties to be exposed by more-public initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise initializer corresponding to the properties specified (it also gives you a computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming partial initializers and omitting the `args` tuple property that would be synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”) {
    self.x = x
    self.y = y
    self.name <http://self.name/> = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use cases for memberwise initialization while remaining concise and arguably more clear (because the property list explicitly states which members participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including concise forwarding of parameters to a super or member initializer, or forwarding from a convenience initializer that just needs to provide a few direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise initialization logic
3. Computed tuple properties for each propertylist. (and possibly additional memberwise features in the future if we identify any that would also be generally useful)

In my opinion this is a huge win for both initialization as well as other parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not, but if I allow myself to speak outside of my expertise, I imagine that it's gonna be a lot more work than memberwise initializers because this requires inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly provided arguments with the parameter list of any callee overloads that are in scope and determining whether:

1. There are no overloads for which the provided arguments could be part of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default argument values, and possibly (but very desirable IMO) a subset of parameters there is no way to avoid this logic. I am not an expert at the implementation of such features, but I don’t think it is excessively complex next to other logic implemented in the compiler.

At this point, I feel that a competent macro system is a better investment than adding distinct bits of automation wherever there appears to be repetition.

I agree that a macro system would be great, but it is explicitly not in scope for Swift 3. It would also not be capable of implementing parameter forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(TJ Usiyan) #11

func _computeLastParam() -> String {

    return "I am the last of my line"

}

func foo(first: Int, second: String, last: String = _computeLastParam()) {

    print("first:\(first)")

    print("second:\(second)")

    print("last:\(last)")

}

foo(1, second: "two!")

"

*first:1*

*second:two!*

*last:I am the last of my line*

"

// hopefully?

constexpr func _computeLastParam(input: Int) -> String {

    return "I am the last of my line and you gave me \(input)"

}

func foo(first: Int, second: String, last: String =
_computeLastParam(first)) {

    print("first:\(first)")

    print("second:\(second)")

    print("last:\(last)")

}

foo(1, second: "two!")

"

*first:1*

*second:two!*

*last:I am the last of my line and you gave me 1*

"

···

On Mon, Jan 11, 2016 at 11:05 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 11, 2016, at 10:03 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

I can't get behind this feature. Beside the lack of payoff for added
syntax, the biggest problem is that this is mostly handled by default
values. If we had (once we get?) constant expressions, I imagine that we
might even be able to reference other parameters in default value method
calls–which seems like a more generally useful fix for the issue.

How is this handled by default values? What you’re saying doesn’t make
sense to me. Can you provide an example of what you have in mind?

Have you used a language with a similar feature? Did you not find it
useful?

Matthew

TJ

On Mon, Jan 11, 2016 at 10:29 AM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

I was okay with memberwise initializers but this pushes me beyond my
comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to
features in other languages that are generally considered to be quite
useful.

For example, most dynamic languages have the ability to pack arguments.
There has been discussion of adding tuple packing and unpacking to Swift
but it wouldn’t offer everything it does in dynamic languages (as far as I
can tell).

Swift is statically typed so we would have to specify a type for the
tuple to pack / unpack. This means it must be a fixed list of arguments.
This is not the case in dynamic languages, where whatever arguments the
caller provides are forwarded.

Also, because the packed tuple parameter is declared manually, there
would not be a way to forward a default argument value for specific members
of the tuple as far as I can see (I think a default would need to be
specified for the entire tuple, not just specific members). Even if there
were a way to specify a value for specific members, I don’t believe it
would be possible to “forward” the default value specified by the receiving
function, which is actually what is desired. In dynamic languages, callers
can just provide a subset of the tuple arguments and the receiving function
detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an
arbitrary set of arguments to a receiving function. This feature of C++
relies on the fact that the body of a template is not checked until it is
expanded. This allows the caller of the forwarding function to supply any
set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of
forwarding fits the language as the body of a generic function is checked
on its own. I don’t believe there would be a way to specify constraints
that would allow the arguments to be used to call the forwarding function
(I may be wrong about that if a new kind of constraint was introduced to
support this in the future).

The forwarding mechanism in this proposal supports a couple of things
that I think will be quite useful which are not possible under the examples
of tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole
packed tuple
2. Forwarding default parameter values from the forwardee function for
said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to suppress
forwarding of specific parameters where the callee provides a default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers
save you from typing out the init parameters and assignments to each field.
Argument forwarding saves you from spelling out the parameters *more than
once* (because you still need to type them out for the receiving function)
and from *one call*. While I've been annoyed at initializers, I don't think
I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save
explicit declaration of parameters as well as a single action with the
provided argument.

More importantly, forwarding is a general purpose feature that when
combined with partial initializers and property lists can support much more
expressive memberwise initialization than contained in the initial
proposal. There was quite a bit of discussion about both the limitations
of the memberwise initialization proposal as well as the specificity of it
to exactly one use case (memberwise initialization). Forwarding plays a
role in removing the limitations while building on a more general
foundation.

Here’s an example that takes advantage of the combined power of the three
proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init
proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let`
properties
5. Allows more-private properties to be exposed by more-public initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise
initializer corresponding to the properties specified (it also gives you a
computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward
arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming
partial initializers and omitting the `args` tuple property that would be
synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”) {
    self.x = x
    self.y = y
    self.name = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use cases
for memberwise initialization while remaining concise and arguably more
clear (because the property list explicitly states which members
participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including
concise forwarding of parameters to a super or member initializer, or
forwarding from a convenience initializer that just needs to provide a few
direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise
initialization logic
3. Computed tuple properties for each propertylist. (and possibly
additional memberwise features in the future if we identify any that would
also be generally useful)

In my opinion this is a huge win for both initialization as well as other
parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not,
but if I allow myself to speak outside of my expertise, I imagine that it's
gonna be a lot more work than memberwise initializers because this requires
inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly
provided arguments with the parameter list of any callee overloads that are
in scope and determining whether:

1. There are no overloads for which the provided arguments could be part
of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be
part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments
could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default argument
values, and possibly (but very desirable IMO) a subset of parameters there
is no way to avoid this logic. I am not an expert at the implementation of
such features, but I don’t think it is excessively complex next to other
logic implemented in the compiler.

At this point, I feel that a competent macro system is a better
investment than adding distinct bits of automation wherever there appears
to be repetition.

I agree that a macro system would be great, but it is explicitly not in
scope for Swift 3. It would also not be capable of implementing parameter
forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution < >> swift-evolution@swift.org> a écrit :

I have always considered the Flexible Memberwise Initialization proposal
to be just a first step (as evidenced by the many future enhancements it
discussed). Its review has inspired new ideas and helped to shape my
vision of the best long-term solution. My final thoughts about the review
can be found here:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals
describing general features that can work together to form a complete
solution.

The proposal drafts can be found at the following links:

* *Parameter forwarding:*
https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* *Partial initializers:*
https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* *Property lists:*
https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

   - Proposal: SE-NNNN
   <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
   - Author(s): Matthew Johnson <https://github.com/anandabits>
   - Status: *Awaiting review*
   - Review manager: TBD

Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding
<https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the
purpose of forwarding the provided arguments to another function. This
results in reduntant parameter specifications that make code less clear and
less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

   - Convenience initializers that foward parameters directly to a
   designated initializer
   - Designated initializers that foward parameters directly to a super
   initializer
   - Designated initializers that foward parameters directly to a member
   initializer, perhaps in a composition-based design
   - If the partial initilaizer proposal is accepted, designated
   initializers that forward parameters to one or more partial initializers

NOTE: I haven’t had time to think too much aboue use cases beyond
initialization. Please share examples and I will add them to this proposal.
Proposed solution

The proposed solution is to introduce an automatic parameter forwarding
mechansim. It allows users to provide direct arguments for some parameters
while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Some things to note about the syntax:

   1. ...fooParams is a placeholder introduced with ... and followed by
   an identifier.
   2. In the signature it can be placed anywhere in the parameter list.
   3. At the call site, it must appear at the end of the argument list.
   4. The placeholder matches the parameters not directly provided
   including their external label and default value if those exist.
   5. Parameters corresponding to the matched parameters are synthesized
   by the compiler where the placeholder exists in the parameter list,
   including the default argument if one exists.
   6. The identifier portion of the placeholder may be omitted if only
   one set of forwarded parameters exist within the function.

Additional details will be introduced with a corresponding example.
Omitting the placeholder identifier

The above example can be written more concisely by omitting the
placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...) {
    foo(i: 32, ...)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

NOTE: If the community feels strongly that the identifier should be
required I am willing to do so.
Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of
parameters:

func foo(i i: Int, s: String, f: Float = 42) { }func foo2(d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Direct arguments

Any direct arguments provided in the forwarding call must follow the
usual argument ordering rules, with the only exception being that it is
allowed to omit some arguments that would normally be required. When the
compiler performs forwarding it will insert forwarded arguments in the
correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}
// user writes:func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}
// compiler synthesizes:func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}

Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as
long as the signature of the matched parameters matches exactly, including
any default values.

func foo(i i: Int, s: String, d: Double = 43) { }func bar(i i: Int, s: String, d: Double = 43) { }
// user writes:func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}
// compiler synthesizes: func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}

NOTE: This provision might be controversial. If the community doesn’t
like it or the implementation is too complex I will remove it.
Unambiguous call

When forwarding parameters to a function that is overloaded the caller
must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)

    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}
// compiler synthesizes: func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}

Default values

When forwarding to a function that accepts default values it is possible
to explicitly request the default value. This allows for disambiguation and
also allows the forwarding function to suppress a defaulted parameter from
participating in forwarding without needing to supply a specific value. The
default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}

It is also possible to explicitly request all defaults at once using
default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(default..., ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}

NOTE: The actual implementation of default arguments looks somewhat
different. These examples are intended to communicate the behavior, not the
exact details of implementation.
Generic parameters

If the types of any matched parameters reference any generic type
parameters of the forwardee the generic type parameters must also be
forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(...fooParams)
}
// compiler synthesizes:func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}

If a generic parameter is referenced in a constraint that also references
a generic parameter that will not be forwarded the constraint is resolved
to a concrete type when possible. This may not be possible in all cases.
When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }
// user writes:func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}
// compiler synthesizes:func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}

Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal
names that do not conflict with the internal names of any manually declared
parameters. This applies to both generic type parameter names as well as
value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}
// compiler synthesizes:func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}

Detailed design

TODO but should fall out pretty clearly from the proposed solution
Impact on existing code

This is a strictly additive change. It has no impact on existing code.
Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and
any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I
consider anything reasonably clear and concise to be acceptable.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Hart) #12

I’m already not a huge fan of the complexity introduced with memberwise initializers, but see some usefulness. But I see much less use in parameter forwarding.
I’m very afraid of all this "syntactic sugar” and would prefer a simpler language even if it means a little bit more typing.

···

On 11 Jan 2016, at 19:26, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

I was okay with memberwise initializers but this pushes me beyond my comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to features in other languages that are generally considered to be quite useful.

/* snip */

I don't find it similar to how dynamic languages do parameter packing, and not useful in the same conditions. In Python, you'll use parameter packing and unpacking mostly with functions that receive variable parameters. Swift's variadic functions don't work like that. It's strictly less powerful and less needed than C++'s perfect forwarding because Swift doesn't have lvalue (or rvalue) references. Besides, perfect forwarding is mostly used when you don't know the call target, and the parameter packing proposal requires you to know it.

The fact that there are forwarding solutions in other languages doesn't mean that Swift has the problems that these solutions are trying to address.

I'm not sold on the usefulness of the feature. Memberwise initializers save you from typing out the init parameters and assignments to each field. Argument forwarding saves you from spelling out the parameters *more than once* (because you still need to type them out for the receiving function) and from *one call*. While I've been annoyed at initializers, I don't think I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save explicit declaration of parameters as well as a single action with the provided argument.

Here's a 4-field example for memberwise initializers:

struct Foo {
  var bar: Int
  var baz: Int
  var frob: Int
  var nicate: Int
}

it's either gonna be "memberwise init(...) {}" (23 chars) or:

  init(bar: Int, baz: Int, frob: Int, nicate: Int) {
    self.bar = bar
    self.baz = baz
    self.frob = frob
    self.nicate = nicate
  }

120 characters excluding tabs, and with very short insignificant names. That's like six times less code for the same thing.

Let's have a 4-parameter method:

class Foo {
  func foo(bar: Int, baz: Int, frob: Int, nicate: Int) { /* snip */ }
}

Now this is either going to be "func bar(...fooPack) { foo(bar: 1, ...fooPack) }" (48 chars) or "func bar(baz: Int, frob: Int, nicate: Int) { foo(bar: 1, baz: baz, frob: frob, nicate: nicate) }" (96 chars).

More importantly, forwarding is a general purpose feature that when combined with partial initializers and property lists can support much more expressive memberwise initialization than contained in the initial proposal. There was quite a bit of discussion about both the limitations of the memberwise initialization proposal as well as the specificity of it to exactly one use case (memberwise initialization). Forwarding plays a role in removing the limitations while building on a more general foundation.

I disagree with the critics and I don't support these other proposals. In my opinion, the objections you're referring to were theoretical-style "this doesn't solve the General Problem". The vast majority of users don't need the General Problem to be solved, and if a specific solution gets us 95% of the way there with little disturbance, it's probably best to take it and leave behind the major changes required for the remaining 5%.

The question here is whether memberwise initialization gets us 95% of the way there. I think it does, and I'm gonna need real code and user feedback to change my mind. This is unlikely to happen before memberwise initializers ship.

I really think that we should wait to see how memberwise initialization plays out before laying out plans to extend it.

At this point, I feel that a competent macro system is a better investment than adding distinct bits of automation wherever there appears to be repetition.

I agree that a macro system would be great, but it is explicitly not in scope for Swift 3. It would also not be capable of implementing parameter forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #13

Or is tuple packing and unpacking the best we can hope for?

If we do go the tuple packing/unpacking route, I wonder if we can add syntax that gets the parameter and return types of a function:

I thought about this last night. Something like this might also allow the default parameter values the function declares to appear in the forwarding signature.

With the generalized naming proposal we could get pretty close to forwarding to any function with defaults intact. But where types are required to disambiguate it would still not be possible. Maybe that is an acceptable tradeoff though.

Matthew

···

Sent from my iPad
On Jan 12, 2016, at 12:11 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

   func fooWithLogging(*params: foo.Parameters) -> foo.Return {
       print("Calling foo with \(params)")
       
       let value = foo(*params)
       
       print(" Returned \(value)")
       return value
   }

(Here I'm using a Ruby/Perl 6-style splat, since we're leaning pretty heavily on `...` already.)

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #14

Another major problem with this approach is that it only works if “foo” is unambiguous.

I addressed the fact that the call to foo must be unambiguous further down in the proposal. Did I miss something important? (Not that it matters if this approach is a dead end)

No, you have it covered. I know you’re interested in initializer forwarding in particular, and overloaded initializers are very common, that’s why I raised it as a potential issue.

Makes sense. This is one reason I introduced identifiers in the partial initializers proposal, but certainly not the only reason.

It probably seems like I'm obsessed with initialization by now! I'm really not. Initialization boilerplate just happens to be the first thing I decided to work on after the open source release. I like to see something through when I start on it.

Do you have any ideas for a better direction that could handle forwarding of default parameter values and / or a subset of parameters? Or is tuple packing and unpacking the best we can hope for?

There is no structural problems with mechanisms that take parameters and forward to other calls, e.g.:

func f(a : Int, b : Float, c : String) {
  bar(@@@ARGS@@@) // obviously horrible to get the point across :slight_smile:
}

Related but different: forwarding of varargs is also a common special case. People have wanted to do this for a long time, we just haven’t had cycles to implement it:

func f(a : Int, b : Float…) {
  g(a, …b) // unpack the vars array into a list of parameters.
}

I could see that being useful. Would it have to unpack into a varargs argument since the length is not statically knowable? Valued it also work for any array of the correct type?

If we did that, I could see generalizing it to an arbitrary tuple:

g(x, y, …sometuple, z)

where sometuple’s members got passed as arguments.

That covers cases where the subset of arguments is sequential. I think that would address the vast majority of cases at the site of the forwarding call.

What do you think of the idea Brent and I discussed about being able to reference a function's parameters in a signature? This would allow for a relatively concise way to declare a tuple to forward, and hopefully also include the default arguments.

If we could accomplish both of these it would go pretty far. Would it be worth rewriting the proposal in this manner?

Matthew

···

Sent from my iPad

On Jan 12, 2016, at 12:33 AM, Chris Lattner <clattner@apple.com> wrote:

On Jan 11, 2016, at 6:00 PM, Matthew Johnson <matthew@anandabits.com> wrote:

-Chris


#15

Do you have any ideas for a better direction that could handle forwarding of default parameter values and / or a subset of parameters? Or is tuple packing and unpacking the best we can hope for?

There is no structural problems with mechanisms that take parameters and forward to other calls, e.g.:

func f(a : Int, b : Float, c : String) {
  bar(@@@ARGS@@@) // obviously horrible to get the point across :slight_smile:
}

Really horrible indeed, but this kind of forwarding have the advantage of not obfuscating the parameter list.

The magic behind the forwarding may make it faster to write code, and remove some clutter when the parameter list is exhaustive, but it may be slower to read it. Assuming, you are new to a project, and you look at the following function:

func bar(...fooParams, ...foo2Params)
{
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

How long does it take to find out how to invoke it? The auto-completion within Xcode can help, but what if you using a simpler text editor, or even only reading the code on the Web. In the proposal, the definitions of foo() and foo2() are closed by, but since not limitations are specified, the could be anywhere in the project requiring a possibly painful search for each of them.

Dany St-Amant

···

Le 12 janv. 2016 à 01:33, Chris Lattner via swift-evolution <swift-evolution@swift.org> a écrit :
On Jan 11, 2016, at 6:00 PM, Matthew Johnson <matthew@anandabits.com> wrote:


(Matthew Johnson) #16

This example doesn’t have anything to do with forwarding. Do you have something in mind in the context of parameter forwarding?

Matthew

···

On Jan 11, 2016, at 10:12 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

func _computeLastParam() -> String {
    return "I am the last of my line"
}

func foo(first: Int, second: String, last: String = _computeLastParam()) {
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
first:1
second:two!
last:I am the last of my line
"

// hopefully?

constexpr func _computeLastParam(input: Int) -> String {
    return "I am the last of my line and you gave me \(input)"
}

func foo(first: Int, second: String, last: String = _computeLastParam(first)) {
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
first:1
second:two!
last:I am the last of my line and you gave me 1
"

On Mon, Jan 11, 2016 at 11:05 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 11, 2016, at 10:03 AM, T.J. Usiyan <griotspeak@gmail.com <mailto:griotspeak@gmail.com>> wrote:

I can't get behind this feature. Beside the lack of payoff for added syntax, the biggest problem is that this is mostly handled by default values. If we had (once we get?) constant expressions, I imagine that we might even be able to reference other parameters in default value method calls–which seems like a more generally useful fix for the issue.

How is this handled by default values? What you’re saying doesn’t make sense to me. Can you provide an example of what you have in mind?

Have you used a language with a similar feature? Did you not find it useful?

Matthew

TJ

On Mon, Jan 11, 2016 at 10:29 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

I was okay with memberwise initializers but this pushes me beyond my comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to features in other languages that are generally considered to be quite useful.

For example, most dynamic languages have the ability to pack arguments. There has been discussion of adding tuple packing and unpacking to Swift but it wouldn’t offer everything it does in dynamic languages (as far as I can tell).

Swift is statically typed so we would have to specify a type for the tuple to pack / unpack. This means it must be a fixed list of arguments. This is not the case in dynamic languages, where whatever arguments the caller provides are forwarded.

Also, because the packed tuple parameter is declared manually, there would not be a way to forward a default argument value for specific members of the tuple as far as I can see (I think a default would need to be specified for the entire tuple, not just specific members). Even if there were a way to specify a value for specific members, I don’t believe it would be possible to “forward” the default value specified by the receiving function, which is actually what is desired. In dynamic languages, callers can just provide a subset of the tuple arguments and the receiving function detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an arbitrary set of arguments to a receiving function. This feature of C++ relies on the fact that the body of a template is not checked until it is expanded. This allows the caller of the forwarding function to supply any set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of forwarding fits the language as the body of a generic function is checked on its own. I don’t believe there would be a way to specify constraints that would allow the arguments to be used to call the forwarding function (I may be wrong about that if a new kind of constraint was introduced to support this in the future).

The forwarding mechanism in this proposal supports a couple of things that I think will be quite useful which are not possible under the examples of tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole packed tuple
2. Forwarding default parameter values from the forwardee function for said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to suppress forwarding of specific parameters where the callee provides a default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers save you from typing out the init parameters and assignments to each field. Argument forwarding saves you from spelling out the parameters *more than once* (because you still need to type them out for the receiving function) and from *one call*. While I've been annoyed at initializers, I don't think I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save explicit declaration of parameters as well as a single action with the provided argument.

More importantly, forwarding is a general purpose feature that when combined with partial initializers and property lists can support much more expressive memberwise initialization than contained in the initial proposal. There was quite a bit of discussion about both the limitations of the memberwise initialization proposal as well as the specificity of it to exactly one use case (memberwise initialization). Forwarding plays a role in removing the limitations while building on a more general foundation.

Here’s an example that takes advantage of the combined power of the three proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let` properties
5. Allows more-private properties to be exposed by more-public initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise initializer corresponding to the properties specified (it also gives you a computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming partial initializers and omitting the `args` tuple property that would be synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”) {
    self.x = x
    self.y = y
    self.name <http://self.name/> = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use cases for memberwise initialization while remaining concise and arguably more clear (because the property list explicitly states which members participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including concise forwarding of parameters to a super or member initializer, or forwarding from a convenience initializer that just needs to provide a few direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise initialization logic
3. Computed tuple properties for each propertylist. (and possibly additional memberwise features in the future if we identify any that would also be generally useful)

In my opinion this is a huge win for both initialization as well as other parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not, but if I allow myself to speak outside of my expertise, I imagine that it's gonna be a lot more work than memberwise initializers because this requires inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly provided arguments with the parameter list of any callee overloads that are in scope and determining whether:

1. There are no overloads for which the provided arguments could be part of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default argument values, and possibly (but very desirable IMO) a subset of parameters there is no way to avoid this logic. I am not an expert at the implementation of such features, but I don’t think it is excessively complex next to other logic implemented in the compiler.

At this point, I feel that a competent macro system is a better investment than adding distinct bits of automation wherever there appears to be repetition.

I agree that a macro system would be great, but it is explicitly not in scope for Swift 3. It would also not be capable of implementing parameter forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(TJ Usiyan) #17

The last bit might not but default values have *everything* to do with
parameter forwarding. What do default values afford us in our API?

foo()
foo(first: Int)
foo(first: Int, second: String)

with 'one' implementation.

What does parameter forwarding get us?

foo()
foo(first: Int)
foo(first: Int, second: String)

with 'one' implementation (but maybe in reverse?)

···

On Mon, Jan 11, 2016 at 11:15 AM, Matthew Johnson <matthew@anandabits.com> wrote:

This example doesn’t have anything to do with forwarding. Do you have
something in mind in the context of parameter forwarding?

Matthew

On Jan 11, 2016, at 10:12 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

func _computeLastParam() -> String {
    return "I am the last of my line"
}

func foo(first: Int, second: String, last: String = _computeLastParam()) {
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
*first:1*
*second:two!*
*last:I am the last of my line*
"

// hopefully?

constexpr func _computeLastParam(input: Int) -> String {
    return "I am the last of my line and you gave me \(input)"
}

func foo(first: Int, second: String, last: String =
_computeLastParam(first)) {
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
*first:1*
*second:two!*
*last:I am the last of my line and you gave me 1*
"

On Mon, Jan 11, 2016 at 11:05 AM, Matthew Johnson <matthew@anandabits.com> > wrote:

On Jan 11, 2016, at 10:03 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

I can't get behind this feature. Beside the lack of payoff for added
syntax, the biggest problem is that this is mostly handled by default
values. If we had (once we get?) constant expressions, I imagine that we
might even be able to reference other parameters in default value method
calls–which seems like a more generally useful fix for the issue.

How is this handled by default values? What you’re saying doesn’t make
sense to me. Can you provide an example of what you have in mind?

Have you used a language with a similar feature? Did you not find it
useful?

Matthew

TJ

On Mon, Jan 11, 2016 at 10:29 AM, Matthew Johnson via swift-evolution < >> swift-evolution@swift.org> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

I was okay with memberwise initializers but this pushes me beyond my
comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to
features in other languages that are generally considered to be quite
useful.

For example, most dynamic languages have the ability to pack arguments.
There has been discussion of adding tuple packing and unpacking to Swift
but it wouldn’t offer everything it does in dynamic languages (as far as I
can tell).

Swift is statically typed so we would have to specify a type for the
tuple to pack / unpack. This means it must be a fixed list of arguments.
This is not the case in dynamic languages, where whatever arguments the
caller provides are forwarded.

Also, because the packed tuple parameter is declared manually, there
would not be a way to forward a default argument value for specific members
of the tuple as far as I can see (I think a default would need to be
specified for the entire tuple, not just specific members). Even if there
were a way to specify a value for specific members, I don’t believe it
would be possible to “forward” the default value specified by the receiving
function, which is actually what is desired. In dynamic languages, callers
can just provide a subset of the tuple arguments and the receiving function
detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an
arbitrary set of arguments to a receiving function. This feature of C++
relies on the fact that the body of a template is not checked until it is
expanded. This allows the caller of the forwarding function to supply any
set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of
forwarding fits the language as the body of a generic function is checked
on its own. I don’t believe there would be a way to specify constraints
that would allow the arguments to be used to call the forwarding function
(I may be wrong about that if a new kind of constraint was introduced to
support this in the future).

The forwarding mechanism in this proposal supports a couple of things
that I think will be quite useful which are not possible under the examples
of tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole
packed tuple
2. Forwarding default parameter values from the forwardee function for
said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to
suppress forwarding of specific parameters where the callee provides a
default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers
save you from typing out the init parameters and assignments to each field.
Argument forwarding saves you from spelling out the parameters *more than
once* (because you still need to type them out for the receiving function)
and from *one call*. While I've been annoyed at initializers, I don't think
I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save
explicit declaration of parameters as well as a single action with the
provided argument.

More importantly, forwarding is a general purpose feature that when
combined with partial initializers and property lists can support much more
expressive memberwise initialization than contained in the initial
proposal. There was quite a bit of discussion about both the limitations
of the memberwise initialization proposal as well as the specificity of it
to exactly one use case (memberwise initialization). Forwarding plays a
role in removing the limitations while building on a more general
foundation.

Here’s an example that takes advantage of the combined power of the
three proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init
proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let`
properties
5. Allows more-private properties to be exposed by more-public
initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise
initializer corresponding to the properties specified (it also gives you a
computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward
arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming
partial initializers and omitting the `args` tuple property that would be
synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”) {
    self.x = x
    self.y = y
    self.name = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use cases
for memberwise initialization while remaining concise and arguably more
clear (because the property list explicitly states which members
participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including
concise forwarding of parameters to a super or member initializer, or
forwarding from a convenience initializer that just needs to provide a few
direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise
initialization logic
3. Computed tuple properties for each propertylist. (and possibly
additional memberwise features in the future if we identify any that would
also be generally useful)

In my opinion this is a huge win for both initialization as well as
other parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not,
but if I allow myself to speak outside of my expertise, I imagine that it's
gonna be a lot more work than memberwise initializers because this requires
inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly
provided arguments with the parameter list of any callee overloads that are
in scope and determining whether:

1. There are no overloads for which the provided arguments could be part
of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be
part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments
could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default argument
values, and possibly (but very desirable IMO) a subset of parameters there
is no way to avoid this logic. I am not an expert at the implementation of
such features, but I don’t think it is excessively complex next to other
logic implemented in the compiler.

At this point, I feel that a competent macro system is a better
investment than adding distinct bits of automation wherever there appears
to be repetition.

I agree that a macro system would be great, but it is explicitly not in
scope for Swift 3. It would also not be capable of implementing parameter
forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution < >>> swift-evolution@swift.org> a écrit :

I have always considered the Flexible Memberwise Initialization proposal
to be just a first step (as evidenced by the many future enhancements it
discussed). Its review has inspired new ideas and helped to shape my
vision of the best long-term solution. My final thoughts about the review
can be found here:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals
describing general features that can work together to form a complete
solution.

The proposal drafts can be found at the following links:

* *Parameter forwarding:*
https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* *Partial initializers:*
https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* *Property lists:*
https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

   - Proposal: SE-NNNN
   <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
   - Author(s): Matthew Johnson <https://github.com/anandabits>
   - Status: *Awaiting review*
   - Review manager: TBD

Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding
<https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the
purpose of forwarding the provided arguments to another function. This
results in reduntant parameter specifications that make code less clear and
less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

   - Convenience initializers that foward parameters directly to a
   designated initializer
   - Designated initializers that foward parameters directly to a super
   initializer
   - Designated initializers that foward parameters directly to a
   member initializer, perhaps in a composition-based design
   - If the partial initilaizer proposal is accepted, designated
   initializers that forward parameters to one or more partial initializers

NOTE: I haven’t had time to think too much aboue use cases beyond
initialization. Please share examples and I will add them to this proposal.
Proposed solution

The proposed solution is to introduce an automatic parameter forwarding
mechansim. It allows users to provide direct arguments for some parameters
while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Some things to note about the syntax:

   1. ...fooParams is a placeholder introduced with ... and followed by
   an identifier.
   2. In the signature it can be placed anywhere in the parameter list.
   3. At the call site, it must appear at the end of the argument list.
   4. The placeholder matches the parameters not directly provided
   including their external label and default value if those exist.
   5. Parameters corresponding to the matched parameters are
   synthesized by the compiler where the placeholder exists in the parameter
   list, including the default argument if one exists.
   6. The identifier portion of the placeholder may be omitted if only
   one set of forwarded parameters exist within the function.

Additional details will be introduced with a corresponding example.
Omitting the placeholder identifier

The above example can be written more concisely by omitting the
placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...) {
    foo(i: 32, ...)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

NOTE: If the community feels strongly that the identifier should be
required I am willing to do so.
Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of
parameters:

func foo(i i: Int, s: String, f: Float = 42) { }func foo2(d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Direct arguments

Any direct arguments provided in the forwarding call must follow the
usual argument ordering rules, with the only exception being that it is
allowed to omit some arguments that would normally be required. When the
compiler performs forwarding it will insert forwarded arguments in the
correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}
// user writes:func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}
// compiler synthesizes:func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}

Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as
long as the signature of the matched parameters matches exactly, including
any default values.

func foo(i i: Int, s: String, d: Double = 43) { }func bar(i i: Int, s: String, d: Double = 43) { }
// user writes:func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}
// compiler synthesizes: func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}

NOTE: This provision might be controversial. If the community doesn’t
like it or the implementation is too complex I will remove it.
Unambiguous call

When forwarding parameters to a function that is overloaded the caller
must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)

    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}
// compiler synthesizes: func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}

Default values

When forwarding to a function that accepts default values it is possible
to explicitly request the default value. This allows for disambiguation and
also allows the forwarding function to suppress a defaulted parameter from
participating in forwarding without needing to supply a specific value. The
default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}

It is also possible to explicitly request all defaults at once using
default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(default..., ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}

NOTE: The actual implementation of default arguments looks somewhat
different. These examples are intended to communicate the behavior, not the
exact details of implementation.
Generic parameters

If the types of any matched parameters reference any generic type
parameters of the forwardee the generic type parameters must also be
forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(...fooParams)
}
// compiler synthesizes:func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}

If a generic parameter is referenced in a constraint that also
references a generic parameter that will not be forwarded the constraint is
resolved to a concrete type when possible. This may not be possible in all
cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }
// user writes:func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}
// compiler synthesizes:func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}

Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal
names that do not conflict with the internal names of any manually declared
parameters. This applies to both generic type parameter names as well as
value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}
// compiler synthesizes:func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}

Detailed design

TODO but should fall out pretty clearly from the proposed solution
Impact on existing code

This is a strictly additive change. It has no impact on existing code.
Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and
any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I
consider anything reasonably clear and concise to be acceptable.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #18

Related but different: forwarding of varargs is also a common special case. People have wanted to do this for a long time, we just haven’t had cycles to implement it:

func f(a : Int, b : Float…) {
g(a, …b) // unpack the vars array into a list of parameters.
}

I could see that being useful. Would it have to unpack into a varargs argument since the length is not statically knowable?

Yes, exactly.

If we did that, I could see generalizing it to an arbitrary tuple:

g(x, y, …sometuple, z)

where sometuple’s members got passed as arguments.

That covers cases where the subset of arguments is sequential. I think that would address the vast majority of cases at the site of the forwarding call.

What do you think of the idea Brent and I discussed about being able to reference a function's parameters in a signature?

I haven’t had a chance to keep up with the thread. I’m still trying to figure out what to do with memberwise, and you guys are building incredibly intricate castles in the clouds :-)

My one meta-comment is that you should focus on making extensions to swift *small*, *obvious*, *orthogonal* and for them to pay for themselves. In the partial function proposal for example (which I also haven’t had time to read), I saw a comment go by about parameter forwarding. I’d strongly recommend *not* baking any notion of parameter forwarding into that proposal. Doing so makes it more complex and more likely to be rejected.

If you’re interested in building a series of proposals that all work together, then that is great, but it depends on them initial proposals being *accepted* and us getting at least some experience with them first.

-Chris

···

On Jan 12, 2016, at 7:05 AM, Matthew Johnson <matthew@anandabits.com> wrote:


(Matthew Johnson) #19

The last bit might not but default values have *everything* to do with parameter forwarding. What do default values afford us in our API?

foo()
foo(first: Int)
foo(first: Int, second: String)

with 'one' implementation.

What does parameter forwarding get us?

foo()
foo(first: Int)
foo(first: Int, second: String)

with 'one' implementation (but maybe in reverse?)

No, that is not the point of parameter forwarding. Obviously default values are much better in that case. Parameter forwarding is for use cases where you need to forward to an entirely different function. Here are the use cases I listed in the proposal:

  • Convenience initializers that foward parameters directly to a designated initializer
  • Designated initializers that foward parameters directly to a super initializer
  • Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
  • If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers

None of these use cases are handled by simple default values.

There are use cases, I just haven’t had time to put together a more comprehensive motivation section yet.

Matthew

···

On Jan 11, 2016, at 11:13 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

On Mon, Jan 11, 2016 at 11:15 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:
This example doesn’t have anything to do with forwarding. Do you have something in mind in the context of parameter forwarding?

Matthew

On Jan 11, 2016, at 10:12 AM, T.J. Usiyan <griotspeak@gmail.com <mailto:griotspeak@gmail.com>> wrote:

func _computeLastParam() -> String {
    return "I am the last of my line"
}

func foo(first: Int, second: String, last: String = _computeLastParam()) {
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
first:1
second:two!
last:I am the last of my line
"

// hopefully?

constexpr func _computeLastParam(input: Int) -> String {
    return "I am the last of my line and you gave me \(input)"
}

func foo(first: Int, second: String, last: String = _computeLastParam(first)) {
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
first:1
second:two!
last:I am the last of my line and you gave me 1
"

On Mon, Jan 11, 2016 at 11:05 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 11, 2016, at 10:03 AM, T.J. Usiyan <griotspeak@gmail.com <mailto:griotspeak@gmail.com>> wrote:

I can't get behind this feature. Beside the lack of payoff for added syntax, the biggest problem is that this is mostly handled by default values. If we had (once we get?) constant expressions, I imagine that we might even be able to reference other parameters in default value method calls–which seems like a more generally useful fix for the issue.

How is this handled by default values? What you’re saying doesn’t make sense to me. Can you provide an example of what you have in mind?

Have you used a language with a similar feature? Did you not find it useful?

Matthew

TJ

On Mon, Jan 11, 2016 at 10:29 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

I was okay with memberwise initializers but this pushes me beyond my comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to features in other languages that are generally considered to be quite useful.

For example, most dynamic languages have the ability to pack arguments. There has been discussion of adding tuple packing and unpacking to Swift but it wouldn’t offer everything it does in dynamic languages (as far as I can tell).

Swift is statically typed so we would have to specify a type for the tuple to pack / unpack. This means it must be a fixed list of arguments. This is not the case in dynamic languages, where whatever arguments the caller provides are forwarded.

Also, because the packed tuple parameter is declared manually, there would not be a way to forward a default argument value for specific members of the tuple as far as I can see (I think a default would need to be specified for the entire tuple, not just specific members). Even if there were a way to specify a value for specific members, I don’t believe it would be possible to “forward” the default value specified by the receiving function, which is actually what is desired. In dynamic languages, callers can just provide a subset of the tuple arguments and the receiving function detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an arbitrary set of arguments to a receiving function. This feature of C++ relies on the fact that the body of a template is not checked until it is expanded. This allows the caller of the forwarding function to supply any set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of forwarding fits the language as the body of a generic function is checked on its own. I don’t believe there would be a way to specify constraints that would allow the arguments to be used to call the forwarding function (I may be wrong about that if a new kind of constraint was introduced to support this in the future).

The forwarding mechanism in this proposal supports a couple of things that I think will be quite useful which are not possible under the examples of tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole packed tuple
2. Forwarding default parameter values from the forwardee function for said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to suppress forwarding of specific parameters where the callee provides a default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers save you from typing out the init parameters and assignments to each field. Argument forwarding saves you from spelling out the parameters *more than once* (because you still need to type them out for the receiving function) and from *one call*. While I've been annoyed at initializers, I don't think I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save explicit declaration of parameters as well as a single action with the provided argument.

More importantly, forwarding is a general purpose feature that when combined with partial initializers and property lists can support much more expressive memberwise initialization than contained in the initial proposal. There was quite a bit of discussion about both the limitations of the memberwise initialization proposal as well as the specificity of it to exactly one use case (memberwise initialization). Forwarding plays a role in removing the limitations while building on a more general foundation.

Here’s an example that takes advantage of the combined power of the three proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let` properties
5. Allows more-private properties to be exposed by more-public initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise initializer corresponding to the properties specified (it also gives you a computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming partial initializers and omitting the `args` tuple property that would be synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”) {
    self.x = x
    self.y = y
    self.name <http://self.name/> = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use cases for memberwise initialization while remaining concise and arguably more clear (because the property list explicitly states which members participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including concise forwarding of parameters to a super or member initializer, or forwarding from a convenience initializer that just needs to provide a few direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise initialization logic
3. Computed tuple properties for each propertylist. (and possibly additional memberwise features in the future if we identify any that would also be generally useful)

In my opinion this is a huge win for both initialization as well as other parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not, but if I allow myself to speak outside of my expertise, I imagine that it's gonna be a lot more work than memberwise initializers because this requires inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly provided arguments with the parameter list of any callee overloads that are in scope and determining whether:

1. There are no overloads for which the provided arguments could be part of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default argument values, and possibly (but very desirable IMO) a subset of parameters there is no way to avoid this logic. I am not an expert at the implementation of such features, but I don’t think it is excessively complex next to other logic implemented in the compiler.

At this point, I feel that a competent macro system is a better investment than adding distinct bits of automation wherever there appears to be repetition.

I agree that a macro system would be great, but it is explicitly not in scope for Swift 3. It would also not be capable of implementing parameter forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals describing general features that can work together to form a complete solution.

The proposal drafts can be found at the following links:

* Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Awaiting review
Review manager: TBD
Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding <https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for the purpose of forwarding the provided arguments to another function. This results in reduntant parameter specifications that make code less clear and less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

Convenience initializers that foward parameters directly to a designated initializer
Designated initializers that foward parameters directly to a super initializer
Designated initializers that foward parameters directly to a member initializer, perhaps in a composition-based design
If the partial initilaizer proposal is accepted, designated initializers that forward parameters to one or more partial initializers
NOTE: I haven’t had time to think too much aboue use cases beyond initialization. Please share examples and I will add them to this proposal.

Proposed solution

The proposed solution is to introduce an automatic parameter forwarding mechansim. It allows users to provide direct arguments for some parameters while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Some things to note about the syntax:

...fooParams is a placeholder introduced with ... and followed by an identifier.
In the signature it can be placed anywhere in the parameter list.
At the call site, it must appear at the end of the argument list.
The placeholder matches the parameters not directly provided including their external label and default value if those exist.
Parameters corresponding to the matched parameters are synthesized by the compiler where the placeholder exists in the parameter list, including the default argument if one exists.
The identifier portion of the placeholder may be omitted if only one set of forwarded parameters exist within the function.
Additional details will be introduced with a corresponding example.

Omitting the placeholder identifier

The above example can be written more concisely by omitting the placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...) {
    foo(i: 32, ...)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
NOTE: If the community feels strongly that the identifier should be required I am willing to do so.

Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of parameters:

func foo(i i: Int, s: String, f: Float = 42) { }
func foo2(d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}

// compiler synthesizes:
func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}
Direct arguments

Any direct arguments provided in the forwarding call must follow the usual argument ordering rules, with the only exception being that it is allowed to omit some arguments that would normally be required. When the compiler performs forwarding it will insert forwarded arguments in the correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }

func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}

// user writes:
func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}

// compiler synthesizes:
func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}
Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls as long as the signature of the matched parameters matches exactly, including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }
func bar(i i: Int, s: String, d: Double = 43) { }

// user writes:
func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}

// compiler synthesizes:
func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}
NOTE: This provision might be controversial. If the community doesn’t like it or the implementation is too complex I will remove it.

Unambiguous call

When forwarding parameters to a function that is overloaded the caller must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)
    
    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}
Default values

When forwarding to a function that accepts default values it is possible to explicitly request the default value. This allows for disambiguation and also allows the forwarding function to suppress a defaulted parameter from participating in forwarding without needing to supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }

// user writes:
func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}
It is also possible to explicitly request all defaults at once using default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(default..., ...fooParams)
}

// compiler synthesizes:
func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}
NOTE: The actual implementation of default arguments looks somewhat different. These examples are intended to communicate the behavior, not the exact details of implementation.

Generic parameters

If the types of any matched parameters reference any generic type parameters of the forwardee the generic type parameters must also be forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar(...fooParams) {
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}
If a generic parameter is referenced in a constraint that also references a generic parameter that will not be forwarded the constraint is resolved to a concrete type when possible. This may not be possible in all cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }

// user writes:
func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}

// compiler synthesizes:
func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}
Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal names that do not conflict with the internal names of any manually declared parameters. This applies to both generic type parameter names as well as value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }

// user writes:
func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}

// compiler synthesizes:
func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}
Detailed design

TODO but should fall out pretty clearly from the proposed solution

Impact on existing code

This is a strictly additive change. It has no impact on existing code.

Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I consider anything reasonably clear and concise to be acceptable.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(TJ Usiyan) #20

I am not sure that I see the value in *not* explicitly passing those
parameters on in a call, then. Needing to l keep track of the parameter
list to pass on to an admittedly different method seems *too* implicit.

···

On Mon, Jan 11, 2016 at 12:17 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 11, 2016, at 11:13 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

The last bit might not but default values have *everything* to do with
parameter forwarding. What do default values afford us in our API?

foo()
foo(first: Int)
foo(first: Int, second: String)

with 'one' implementation.

What does parameter forwarding get us?

foo()
foo(first: Int)
foo(first: Int, second: String)

with 'one' implementation (but maybe in reverse?)

No, that is not the point of parameter forwarding. Obviously default
values are much better in that case. Parameter forwarding is for use cases
where you need to forward to an entirely different function. Here are the
use cases I listed in the proposal:

• Convenience initializers that foward parameters directly to a
designated initializer
• Designated initializers that foward parameters directly to a super
initializer
• Designated initializers that foward parameters directly to a
member initializer, perhaps in a composition-based design
• If the partial initilaizer proposal is accepted, designated initializers
that forward parameters to one or more partial initializers

None of these use cases are handled by simple default values.

There are use cases, I just haven’t had time to put together a more
comprehensive motivation section yet.

Matthew

On Mon, Jan 11, 2016 at 11:15 AM, Matthew Johnson <matthew@anandabits.com> > wrote:

This example doesn’t have anything to do with forwarding. Do you have
something in mind in the context of parameter forwarding?

Matthew

On Jan 11, 2016, at 10:12 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

func _computeLastParam() -> String {
    return "I am the last of my line"
}

func foo(first: Int, second: String, last: String = _computeLastParam())
{
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
*first:1*
*second:two!*
*last:I am the last of my line*
"

// hopefully?

constexpr func _computeLastParam(input: Int) -> String {
    return "I am the last of my line and you gave me \(input)"
}

func foo(first: Int, second: String, last: String =
_computeLastParam(first)) {
    print("first:\(first)")
    print("second:\(second)")
    print("last:\(last)")
}

foo(1, second: "two!")

"
*first:1*
*second:two!*
*last:I am the last of my line and you gave me 1*
"

On Mon, Jan 11, 2016 at 11:05 AM, Matthew Johnson <matthew@anandabits.com >> > wrote:

On Jan 11, 2016, at 10:03 AM, T.J. Usiyan <griotspeak@gmail.com> wrote:

I can't get behind this feature. Beside the lack of payoff for added
syntax, the biggest problem is that this is mostly handled by default
values. If we had (once we get?) constant expressions, I imagine that we
might even be able to reference other parameters in default value method
calls–which seems like a more generally useful fix for the issue.

How is this handled by default values? What you’re saying doesn’t make
sense to me. Can you provide an example of what you have in mind?

Have you used a language with a similar feature? Did you not find it
useful?

Matthew

TJ

On Mon, Jan 11, 2016 at 10:29 AM, Matthew Johnson via swift-evolution < >>> swift-evolution@swift.org> wrote:

On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

I was okay with memberwise initializers but this pushes me beyond my
comfort zone.

Hi Felix, can you elaborate on why? This feature is quite similar to
features in other languages that are generally considered to be quite
useful.

For example, most dynamic languages have the ability to pack
arguments. There has been discussion of adding tuple packing and unpacking
to Swift but it wouldn’t offer everything it does in dynamic languages (as
far as I can tell).

Swift is statically typed so we would have to specify a type for the
tuple to pack / unpack. This means it must be a fixed list of arguments.
This is not the case in dynamic languages, where whatever arguments the
caller provides are forwarded.

Also, because the packed tuple parameter is declared manually, there
would not be a way to forward a default argument value for specific members
of the tuple as far as I can see (I think a default would need to be
specified for the entire tuple, not just specific members). Even if there
were a way to specify a value for specific members, I don’t believe it
would be possible to “forward” the default value specified by the receiving
function, which is actually what is desired. In dynamic languages, callers
can just provide a subset of the tuple arguments and the receiving function
detects missing arguments, filling in a default.

Another example is variadic generics in C++, which can also forward an
arbitrary set of arguments to a receiving function. This feature of C++
relies on the fact that the body of a template is not checked until it is
expanded. This allows the caller of the forwarding function to supply any
set of parameters that would be valid when calling the forwardee.

Even if Swift supported variadic generics I don’t think this method of
forwarding fits the language as the body of a generic function is checked
on its own. I don’t believe there would be a way to specify constraints
that would allow the arguments to be used to call the forwarding function
(I may be wrong about that if a new kind of constraint was introduced to
support this in the future).

The forwarding mechanism in this proposal supports a couple of things
that I think will be quite useful which are not possible under the examples
of tuple packing and unpacking in Swift that I have seen shared thus far:

1. Providing default values for specific parameters, not just a whole
packed tuple
2. Forwarding default parameter values from the forwardee function for
said parameters
3. Forwarding a subset of the forwarded’s parameters
4. Explicitly providing default values for disambiguation and to
suppress forwarding of specific parameters where the callee provides a
default value
5. Forwarding generic parameters

I'm not sold on the usefulness of the feature. Memberwise initializers
save you from typing out the init parameters and assignments to each field.
Argument forwarding saves you from spelling out the parameters *more than
once* (because you still need to type them out for the receiving function)
and from *one call*. While I've been annoyed at initializers, I don't think
I've ever been particularly annoyed at forwarding functions.

Both features save approximately the same amount of code. They save
explicit declaration of parameters as well as a single action with the
provided argument.

More importantly, forwarding is a general purpose feature that when
combined with partial initializers and property lists can support much more
expressive memberwise initialization than contained in the initial
proposal. There was quite a bit of discussion about both the limitations
of the memberwise initialization proposal as well as the specificity of it
to exactly one use case (memberwise initialization). Forwarding plays a
role in removing the limitations while building on a more general
foundation.

Here’s an example that takes advantage of the combined power of the
three proposals I just posted:

struct S {
  var name: String = “"
  private let x, y, z: Int

  propertylist args: left x = 0, top y = 0, name

  init(…args) { z = 0 }
}

This does several things not possible in the current memberwise init
proposal:

1. Supports an arbitrary subset of members
2. Supports an arbitrary order for memberwise parameters
3. Supports arbitrary labels for memberwise parameters
4. Supports arbitrary default values for parameters, including `let`
properties
5. Allows more-private properties to be exposed by more-public
initializer

Here’s how it works:

1. The `propertylist` declaration introduces a partial memberwise
initializer corresponding to the properties specified (it also gives you a
computed tuple property containing the specified properties).
2. The `…args` placeholder causes the primary initializer to forward
arguments to the partial initializer introduced in step 1.

The equivalent manually written code would look like this (assuming
partial initializers and omitting the `args` tuple property that would be
synthesized):

struct S {
  var name: String = “"
  private let x, y, z: Int

  partial init args(left x: Int = 0, top y: Int = 0, name: String = “”)
{
    self.x = x
    self.y = y
    self.name = name
  }

  init(left x: Int = 0, top y: Int = 0, name: String = “”) {
    args.init(left: x, top: y, name: name)
    z = 0
  }
}

These features work together to support the additional desired use
cases for memberwise initialization while remaining concise and arguably
more clear (because the property list explicitly states which members
participate in the memberwise partial initializer).

Because the features supporting this are general we also gain:

1. Forwarding in any function, not just initializers (but including
concise forwarding of parameters to a super or member initializer, or
forwarding from a convenience initializer that just needs to provide a few
direct arguments to the designated initializer and forward the rest).
2. Partial initialization support for shared, but non-memberwise
initialization logic
3. Computed tuple properties for each propertylist. (and possibly
additional memberwise features in the future if we identify any that would
also be generally useful)

In my opinion this is a huge win for both initialization as well as
other parts of our code that might take advantage of these features.

I'll let the parser guys say if it's a lot of work to implement or not,
but if I allow myself to speak outside of my expertise, I imagine that it's
gonna be a lot more work than memberwise initializers because this requires
inspecting the function body to figure out its parameters.

It is a bit more work, sure. It requires matching the explicitly
provided arguments with the parameter list of any callee overloads that are
in scope and determining whether:

1. There are no overloads for which the provided arguments could be
part of a valid call. Compiler error.
2. There is a single overload for which the provided arguments could be
part of a valid call. Forward the remaining arguments.
3. There are more than one overloads for which the provided arguments
could be part of a valid call. Compiler error due to ambiguity.

If we want a forwarding mechanism capable of forwarding default
argument values, and possibly (but very desirable IMO) a subset of
parameters there is no way to avoid this logic. I am not an expert at the
implementation of such features, but I don’t think it is excessively
complex next to other logic implemented in the compiler.

At this point, I feel that a competent macro system is a better
investment than adding distinct bits of automation wherever there appears
to be repetition.

I agree that a macro system would be great, but it is explicitly not in
scope for Swift 3. It would also not be capable of implementing parameter
forwarding as described in this proposal.

I hope you will consider discussing this further.

Matthew

Félix

Le 10 janv. 2016 à 22:44:36, Matthew Johnson via swift-evolution < >>>> swift-evolution@swift.org> a écrit :

I have always considered the Flexible Memberwise Initialization
proposal to be just a first step (as evidenced by the many future
enhancements it discussed). Its review has inspired new ideas and helped
to shape my vision of the best long-term solution. My final thoughts about
the review can be found here:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html

Parameter forwarding is the first in a series of three proposals
describing general features that can work together to form a complete
solution.

The proposal drafts can be found at the following links:

* *Parameter forwarding:*
https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md
* *Partial initializers:*
https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md
* *Property lists:*
https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md

Matthew
Parameter Forwarding

   - Proposal: SE-NNNN
   <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-parameter-forwarding.md>
   - Author(s): Matthew Johnson <https://github.com/anandabits>
   - Status: *Awaiting review*
   - Review manager: TBD

Introduction

This feature introduces an automatic parameter forwarding mechanism.

Swift-evolution thread: Proposal Draft: parameter forwarding
<https://lists.swift.org/pipermail/swift-evolution>
Motivation

There are many cases where a function declares parameters simply for
the purpose of forwarding the provided arguments to another function. This
results in reduntant parameter specifications that make code less clear and
less readable by obscuring the simple forwarding that is actually happening.

This feature will be especially useful in initializers such as:

   - Convenience initializers that foward parameters directly to a
   designated initializer
   - Designated initializers that foward parameters directly to a
   super initializer
   - Designated initializers that foward parameters directly to a
   member initializer, perhaps in a composition-based design
   - If the partial initilaizer proposal is accepted, designated
   initializers that forward parameters to one or more partial initializers

NOTE: I haven’t had time to think too much aboue use cases beyond
initialization. Please share examples and I will add them to this proposal.
Proposed solution

The proposed solution is to introduce an automatic parameter forwarding
mechansim. It allows users to provide direct arguments for some parameters
while forwarding others.

The basic mechanism looks like this:

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Some things to note about the syntax:

   1. ...fooParams is a placeholder introduced with ... and followed
   by an identifier.
   2. In the signature it can be placed anywhere in the parameter list.
   3. At the call site, it must appear at the end of the argument list.
   4. The placeholder matches the parameters not directly provided
   including their external label and default value if those exist.
   5. Parameters corresponding to the matched parameters are
   synthesized by the compiler where the placeholder exists in the parameter
   list, including the default argument if one exists.
   6. The identifier portion of the placeholder may be omitted if only
   one set of forwarded parameters exist within the function.

Additional details will be introduced with a corresponding example.
Omitting the placeholder identifier

The above example can be written more concisely by omitting the
placeholder identifier.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...) {
    foo(i: 32, ...)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

NOTE: If the community feels strongly that the identifier should be
required I am willing to do so.
Multiple forwarded parameter sets

It is possible for a single function to forward more than one set of
parameters:

func foo(i i: Int, s: String, f: Float = 42) { }func foo2(d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams, ...foo2Params) {
    foo2(...foo2Params)
    foo(i: 32, ...fooParams)
}
// compiler synthesizes:func bar(s: String, f: Float = 42, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: f, d: d, b: b)
}

Direct arguments

Any direct arguments provided in the forwarding call must follow the
usual argument ordering rules, with the only exception being that it is
allowed to omit some arguments that would normally be required. When the
compiler performs forwarding it will insert forwarded arguments in the
correct location.

func foo(i i: Int, s: String, f: Float = 42, d: Double = 43, b: Bool = false) { }
func bar(...fooParams) {
    // error: `i` must precede `s` in the argument list
    foo(s: "hello", i: 32, ...fooParams)
}
// user writes:func bar(...fooParams) {
    foo(i: 32, f: 0, ...fooParams)
}
// compiler synthesizes:func bar(s s: String, d: Double = 43, b: Bool = false) {
    foo(i: 32, s: s, f: 0, d: d, b: b)
}

Multi-forwarding the same parameters

It is allowed to use the same identifier in multiple forwarding calls
as long as the signature of the matched parameters matches exactly,
including any default values.

func foo(i i: Int, s: String, d: Double = 43) { }func bar(i i: Int, s: String, d: Double = 43) { }
// user writes:func baz(...fooBarParams) {
    foo(...fooBarParams)
    bar(...fooBarParams)
}
// compiler synthesizes: func baz(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d)
    bar(i: i, s: s, d: d)
}

NOTE: This provision might be controversial. If the community doesn’t
like it or the implementation is too complex I will remove it.
Unambiguous call

When forwarding parameters to a function that is overloaded the caller
must provide enough direct arguments to make the call unambiguous.

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // error: ambiguous use of foo
    // foo(i: 32, ...fooParams)

    // ok: `b` makes the call to foo unambiguous
    foo(b: true, ...fooParams)
    // ok: `f` makes the call to foo unambiguous
    foo(f: 24, ...fooParams)
}
// compiler synthesizes: func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: true)
    foo(i: i, s: s, d: d, f: 24)
}

Default values

When forwarding to a function that accepts default values it is
possible to explicitly request the default value. This allows for
disambiguation and also allows the forwarding function to suppress a
defaulted parameter from participating in forwarding without needing to
supply a specific value. The default keyword is used to do this.

We can modify the previous example to use the defualt values:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }func foo(i i: Int, s: String, d: Double = 43, f: Float = 42) { }
// user writes:func bar(...fooParams) {
    // ok: `b` makes the call to foo unambiguous, still uses default value
    foo(b: default, ...fooParams)
    // ok: `f` makes the call to foo unambiguous, still uses default value
    foo(f: default, ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String, d: Double = 43) {
    foo(i: i, s: s, d: d, b: false)
    foo(i: i, s: s, d: d, f: 42)
}

It is also possible to explicitly request all defaults at once using
default.... In this example, foois not overloaded:

func foo(i i: Int, s: String, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(default..., ...fooParams)
}
// compiler synthesizes:func bar(i i: Int, s: String) {
    foo(i: i, s: s, d: 43, b: false)
}

NOTE: The actual implementation of default arguments looks somewhat
different. These examples are intended to communicate the behavior, not the
exact details of implementation.
Generic parameters

If the types of any matched parameters reference any generic type
parameters of the forwardee the generic type parameters must also be
forwarded, along with any constraints on those generic parameters.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar(...fooParams) {
    foo(...fooParams)
}
// compiler synthesizes:func bar<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) {
    foo(i: i, s: s, t: t, d: d, b: b)
}

If a generic parameter is referenced in a constraint that also
references a generic parameter that will not be forwarded the constraint is
resolved to a concrete type when possible. This may not be possible in all
cases. When it is not possible a compiler error will be necessary.

func foo<S: SequenceType, T: SequenceType where S.Generator.Element == T.Generator.Element>
    (s: S, t: T) { }
// user writes:func bar(...fooParams) {
    foo(t: [42], ...fooParams)
}
// compiler synthesizes:func bar<S: SequenceType where S.Generator.Element == Int>(s: S) {
    foo(s: s, t: [42])
}

Syntheszied internal names

The compiler must ensure that all synthesized parameters have internal
names that do not conflict with the internal names of any manually declared
parameters. This applies to both generic type parameter names as well as
value arguments in the parameter list of the function.

func foo<T>(i i: Int, s: String, t: T, d: Double = 43, b: Bool = false) { }
// user writes:func bar<T>(t: T, ...fooParams) {
    // do something with t
    foo(...fooParams)
}
// compiler synthesizes:func bar<T, InternalCompilerIdentifier>(t: T, i i: Int, s: String, t internalCompilerIdentifier: InternalCompilerIdentifier, d: Double = 43, b: Bool = false) {
    foo(t: t, i: i, s: s, t: internalCompilerIdentifier, d: d, b: b)
}

Detailed design

TODO but should fall out pretty clearly from the proposed solution
Impact on existing code

This is a strictly additive change. It has no impact on existing code.
Alternatives considered

I believe the forwarding mechanism itself is pretty straightforward and
any alternatives would be lose functionality without good reason.

The placeholder syntax is of course fair game for bikeshedding. I
consider anything reasonably clear and concise to be acceptable.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution