[swift-evolution] Proposal: Remove implicit tuple splat behavior from function applications

Greg Titus greg at omnigroup.com
Wed Jan 27 10:37:22 CST 2016


+1.

Removing implicit tuple splat will also fix most of the confusing argument issues with anonymous closures.

You wrote a couple weeks back:
----
    func foo(val: Int) { }

    func bar(closure: (Int,Int) -> Void) {
        closure(0, 1)
    }

    bar { foo($0) }       // compiler error
    bar { foo($1) }       // just dandy
    bar { foo($0 + $1) }  // also works
—-

The reason why the compiler error happens is because it is currently legal for $0 to be a tuple of type (Int,Int) and to call foo() with that tuple because of the implicit splatting. Once that is illegal, having the anonymous closure take a single (Int,Int) tuple isn’t a legal possibility, and so you will probably end up with a more understandable error about bar() being called with an argument of  (_) -> Void instead of the expected (Int, Int) -> Void. (I.e. the compiler thinks that there’s just one argument to the closure still, but at least it’s narrowed down the type problem to the correct location.)

And now that the problem will be narrowed to the type signature of the anonymous closure, it becomes possible to fix it (in a separate proposal) by making type inference on anonymous closures implicitly allow additional ignored arguments.  

	- Greg

> On Jan 27, 2016, at 8:06 AM, Paul Cantrell via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I do think there’s eventual value in this (modulo syntax refinement):
> 
> 	foo(*x)    // NOT a serious syntax proposal
> 
> I have hit just a few proxy-flavored situations where argument forwarding was useful. However, in those situations, it’s very little manual effort to just rebuild the tuple: f(tuple.a, b: tuple.b). I thus don’t consider that feature urgent, and would happily wait until Swift 4+ for it. I’d just like to keep a toe in the door for it.
> 
> With that minor caveat, I support this proposal. I’ve been bitten by the Any-related ambiguities Chris mentions — far more often than I’ve actually had use for this feature.
> 
> I’ll also reiterate my wish to remove the overloaded meaning of $0 as the unsplatted args of a closure. It’s equally esoteric and super confusing. Hmm, I should bump that thread….
> 
> Cheers, P
> 
> 
>> On Jan 27, 2016, at 12:23 AM, Chris Lattner via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> For discussion: comments appreciated!
>> 
>> 
>> 
>> 
>> Remove implicit tuple splat behavior from function applications
>> 
>> 	• Proposal: SE-TBD
>> 	• Author(s): Chris Lattner
>> 	• Status: Awaiting review
>> 	• Review manager: TBD
>> Introduction
>> 
>> Function calls (which include several syntactic forms that apply an argument list to something of function type) currently have a dual nature in Swift.  Given something like:
>> 
>> 
>> 	func foo(a : Int, b : Int) {}
>> 
>> 
>> You can call it either with with the typical syntactic form that passes arguments to each of its parameters:
>> 
>> 
>> 	foo(42, b : 17)
>> 
>> or you can take advantage of a little-known feature to pass an entire argument list as a single value (of tuple type):
>> 
>> 	let x = (1, b: 2)
>> 	foo(x)
>> 
>> 
>> This proposal recommends removing the later form, which I affectionately refer to as the “tuple splat” form.  This feature is purely a sugar feature, it does not provide any expressive ability beyond passing the parameters manually.
>> 
>> 
>> Swift-evolution thread: TBD
>> 
>> Motivation
>> 
>> This behavior is cute, precedented in other functional languages, and has some advantages, but it also has several major disadvantages, which are all related to its syntactic form.
>> 
>> * A call to foo(x) looks like a call to an overloaded version of foo, both to the compiler and to the human who maintains the code.  This is extremely confusing if you don’t know the feature exists.
>> * There are real ambiguities in the syntax, e.g. involving Any arguments and situations where you want to pass a tuple value as a single parameter.
>> * The current implementation has a ton of implementation bugs - it doesn’t work reliably.
>> * The current implementation adds complexity to the type checker, slowing it down and adding maintenance burden.
>> * The current implementation doesn’t work the way we would want a tuple splat operation to work.  For example, arguably, you should be able to call foo with:
>> 
>> 	func bar() -> (Int, Int) { … }
>> 	foo(bar())
>> 
>> … but this is not allowed, since tuple labels are required to line up.  You have to write:
>> 
>> 
>> 
>> 	func bar() -> (Int, b: Int)  { … }
>> 	foo(bar())
>> 
>> 
>> This makes this feature very difficult to use in practice, because you have to _’ize a lot of parameters (violating naming conventions), perform manual shuffling (defeating the sugar benefits of the feature), or add parameter labels to the result of functions (which leads to odd tying between callers and callees).
>> 
>> 
>> The root problem here is that we use exactly the same syntax for both forms of function application.  If the two forms were differentiated (an option considered in “alternatives considered” below) then some of these problems would be defined away.
>> 
>> From a historical perspective, the tuple splat form of function application dates back to very early Swift design (probably introduced in 2010, but possibly 2011) where all function application was of a single value to a function type.  For a large number of reasons (including default arguments, variadic arguments, labels, etc) we have completely abandoned this model, but we never came back to reevaluating the tuple splat behavior.
>> 
>> If we didn’t already have this feature, we would not add it to Swift 3 (at least in its current form).
>> 
>> Proposed solution
>> 
>> The proposed solution is simple, we should just remove this feature from the Swift 3 compiler.  Ideally we would deprecate it in the Swift 2.2 compiler and remove it in Swift 3.  However, if there isn’t time to get the deprecation into Swift 2.2, the author believes it would be perfectly fine to just remove it in Swift 3 (with a fixit + migration help of course).
>> 
>> One of the interesting aspect of this feature is that some of the people we’ve spoken to are very fond of it.  However, when pressed, they admit that they are not actually using it widely in their code, or if they are using it, they are abusing naming conventions (distorting their code) in order to use it.  This doesn’t seem like a positive contribution - this seems like a “clever” feature, not a practical one.
>> 
>> Detailed design
>> 
>> The design is straight-forward.  In the Swift 3 time frame, we continue to parse and type check these expressions as we have so far, but produce an error + fixit hint when it is the tuple splat form.  The migrator would auto-apply the fixit hint as it does for other cases.
>> 
>> Impact on existing code
>> 
>> Any code that uses this feature will have to move to the traditional form.  In the case of the example above, this means rewriting the code from:
>> 
>> 	foo(x)
>> 
>> into a form like this:
>> 
>> 	foo(x.0, x.b)
>> 
>> In the case where “x” is a complex expression, a temporary variable will need to be introduced.  We believe that compiler fixits can handle the simple cases directly and that this extension is not widely used.
>> 
>> Alternatives considered
>> 
>> The major problem with this feature is that it was not well considered and implemented properly (owing to its very old age, it has just been kept limping along).  The alternative then is to actually design a proper feature to support this.  Since the implicitness and syntactic ambiguity with normal function application is the problem, the solution is to introduce an explicit syntactic form to represent this.  For example, something like this could address the problems we have:
>> 
>> 	foo(*x)    // NOT a serious syntax proposal
>> 
>> However, actually designing this feature would be a non-trivial effort not core to the Swift 3 mission:
>> 
>> * It is a pure-sugar feature, and therefore low priority.
>> * We don’t have an obvious sigil to use.  “prefix-star” should be kept as unused for now in case we want to use it to refer to memory-related operations in the future.
>> * Making the tuple splat operation great requires more than just fixing the syntactic ambiguities we have, it would require re-evaluating the semantics of the operation (e.g. in light of parameter labels, varargs and other features).
>> 
>> If there is serious interest in pursuing this as a concept, we should do it as a follow-on proposal to this one.  If a good design emerges, we can evaluate that design based on its merits.
>> 
>> 
>> The final alternative is that we could leave the feature in the compiler.  However, that means living with its complexity “forever” or breaking code in the Swift 4 timeframe.  It would be preferable to tackle this breakage in the Swift 3 timeframe, since we know that migration will already be needed then.
>> 
>> -Chris
>> 
>> 
>> _______________________________________________
>> 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



More information about the swift-evolution mailing list