[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