[swift-evolution] [Proposal Draft] parameter forwarding

T.J. Usiyan griotspeak at gmail.com
Mon Jan 11 11:27:24 CST 2016


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

>
> On Jan 11, 2016, at 11:13 AM, T.J. Usiyan <griotspeak at 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 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/13b7d5a6/attachment.html>


More information about the swift-evolution mailing list