[swift-evolution] [Proposal Draft] parameter forwarding

T.J. Usiyan griotspeak at gmail.com
Mon Jan 11 10:12:22 CST 2016


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 at anandabits.com>
wrote:

>
> On Jan 11, 2016, at 10:03 AM, T.J. Usiyan <griotspeak at 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 at swift.org> wrote:
>
>>
>> On Jan 10, 2016, at 10:17 PM, Félix Cloutier <felixcca at 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 at 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 at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160111/bd33ae42/attachment.html>


More information about the swift-evolution mailing list