[swift-evolution] [Proposal Draft] parameter forwarding

T.J. Usiyan griotspeak at gmail.com
Mon Jan 11 11:13:32 CST 2016


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 at 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 at 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 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/45d7f2f9/attachment.html>


More information about the swift-evolution mailing list