Should either, both or none of these two lines compile?

Let's call the two lines mentioned in the title A and B here:

func f(x: Int, _ y: Int) { print(x + y) }
let aTuple = (1, 2)
f(aTuple) // A
f((1, 2)) // B


f(1, 2) should and does obviously compile, but

should A compile?

should B compile?


Details:


In current Swift (Xcode 7.1 beta 3) one of them compiles and the other fails, which I find impossible to wrap my head around.

So I guess this is a (well known) bug, but I filed 23059991 anyway.


I think both A and B should compile, rather than both being errors. And my unconsidered reasoning/feelings behind this has to do with simplicity/elegancy/flexibility/power related to parameter list types, tuples, function/method calling, etc.


This is an example of a very basic part of the language that is (still!) abstruse rather than clear. If it wasn't for things like this, Swift would be great and we would be very productive and happy using it.

I have no evidence, but I feel like B is more likely to be written accidentally than on purpose, perhaps when nesting function calls and putting parens in the wrong place. I suspect that it was deliberately made to be an error, since you are right that your example seems strange at face value. I also wonder about the potential for ambiguity or unexpected results when mixing generic functions with multiple arguments and multiple tuples of various lengths, but I haven't worked through any examples.

In fact, tuples and function arguments have been ambiguous since the first beta of Swift.

var fnc: (Int, Int)->Void = f
fnc(aTuple)  // C
fnc((1, 2))  // D

Do you see how we can declare a closure type which takes one tuple argument?


And I want to note this works:

[(1, 2)].forEach(f)
[aTuple].forEach(f)

Just to further clarify how arcane this basic part of the language currently is, and to explain my utopian view of how simple it should/could(?) be:

let aTuple = (1, 2)

func f(     x: Int, _ y: Int)  -> Void  { print(  x +   y) }
func g( t: (x: Int,   y: Int)) -> Void  { print(t.x + t.y) }
let  h = { (x: Int,   y: Int)  -> Void in print(  x +   y) }

print(f.dynamicType) // Prints (Int, Int) -> ()
print(g.dynamicType) // Prints (Int, Int) -> ()
print(h.dynamicType) // Prints (Int, Int) -> ()
// See those three function types?
// No difference to be seen, which I think is great (as it suggest great simplicity/elegancy/power).

// Now:

// 1. All three can be called with a tuple that is in a variable or constant:
f(aTuple) // OK
g(aTuple) // OK
h(aTuple) // OK

// 2. All three can (sadly NOT) be called with a tuple literal:
f((1, 2)) // Error :´/
g((1, 2)) // OK
h((1, 2)) // Error :´/

// 3. All three can (sadly NOT) be called with "an implicit tuple literal" (the argument list being the tuple literal):
f(1, 2) // OK
g(1, 2) // Error :´/
h(1, 2) // OK


I whish all these would compile, and of course one can come up with reasons for why they shouldn't, but I haven't managed to come up with any that would outweigh the benefits.


Please note/remember that this part of current Swift is incomprehensible/inconsistent to begin with (plain to see in all examples here).

So while it's easy to criticize the current implementation and/or my suggestion, it's probably hard to come up with a solution that is comprehensible/consistent (unless my suggestion is such a solution).

(My problem with both A and B not compiling is, as I guess you already understand, that this must be the only case where:

We get either an error or a successful compilation depending on wether an argument is given as a variable/constant or as a literal.

(inout being an obvious and natural exception).)


Note that:

func f(x: Int, _ y: Int) { print(x + y) }
let aTuple = (1, 2)
f(aTuple) // is OK
f((aTuple)) // is OK
f(((aTuple))) // is OK
f((((aTuple)))) // is OK
// ...
f(((1)), 2) // is OK
f((1), (2)) // is OK
f(1, ((2))) // is OK
// ...

(which I have no trouble with, I think this is OK.)


So I'm not sure the intent of the current behaviour is to catch mistakes when writing nested function calls etc. I'm not even sure that the current behaviour (that both A and B does not compile) is intentional at all.

In fact, tuples and function arguments have been ambiguous since the first beta of Swift.


Yes, I know, and it's strange that (D is an error and that) this inconsistency is still in Swift. If the Swift team can not tell us the intended behaviour, perhaps they can give us a hint of the immense complexity that presumably lies behind it still being inconsistent.

All these examples are just the result of single element tuples not existing, as noted in your other thread.

You can see from your examples that they appear to have deliberately implemented this error, since it's hard to imagine an implementation that is inconsistent in this way accidentally (at least for me). That is why I suggested above that it is possibly related to catching errors, since it's hard for me, and possibly the Swift engineers, to imagine why someone would ever deliberately write f((1,2)) or g(1,2), given the definitions above. I'm having trouble seeing the benefits that you clearly are, unless you only mean the benefit of consistency.

I'm trying to write (large amounts of) actual code / apps in Swift. I'm also trying to be very pragmatic and get things done. I'm certainly not just sitting around nit-picking the language just for spite.


Since the first Swift beta I have been going through these phases (several times):


1. Excited to leave ObjC, C and C++ behind and be able to use a better language that:

Is nicer to read and write.

Allows for higher level abstractions without sacrificing optimizability.

Learns from and aims to teach its users some things from the history of CS that mainstream languages have traditionally failed to learn and teach (until recently, perhaps).


2. Writing code happily making use of features now at my disposal, eg protocol extensions, closures, value types, enums, associated types, etc. And every, single, time: Everything would have worked out great If it wasn't for things like half-baked protocols and protocol extensions with eg associated types not resolving properly, etc., or the inconsistencies mentioned in this thread (I'm not over-using tuples instead of structs).


3. Worrying that Swift will grow into a powerful-if-you-learn-all-this-black-magic-mess like C++, the only substantial difference being that Swift is designed to be safe while C++ is not. Learn and remember a thousand edge cases and obscure tricks in order to be a proficient and productive user. (Don't get me wrong, I sort of enjoy(ed) C++, but the time and code I spent on incorporating those 30 years of backward compatible evolution could have been spent on something else if there had been an alternative).


4. Realizing that to get anything done, I have to accept the current state of Swift by rejecting large parts of the language. These half-baked features will only get me so far, and I can't spend time on keeping up to date with what can and cannot be achieved using them in their current state, it's just a waste of time. So to be productive I have to use only a subset of Swift, and it's almost like being back coding in C (yes, I don't have to write my own memory manegment code, I have closures and I can make basic use of generics instead of using a preprocessor, I'm actually tempted to use a preprocessor for Swift too sometimes, because of all the boilerplate I have to write).


5. Downloading a new beta, seeing that some of the worst issues have been resolved (like when whole module optimization finally arrived, woho! Or protocol extensions!). Regaining trust in Swift, so back to phase 1.



Back to the particular inconsistencies of this thread. I think they add nothing but confusion, and it feels a bit backwards and unneccesary to provide concrete examples of the benefits of having consistency over inconsistency. If it is there to catch mistakes then concrete examples of such mistakes would be more interesting / less obvious.


More generally though, concepts that are simple yet highly recombinable are often (unexpectedly) powerful, like eg legos, bricks, genes, letters, numbers, transistors, objects, functions, etc.


To me, Swift currently seems to suggest that parameter lists and tuples are(was?) supposed to be united as one such simple and powerful concept. But when I try to make actual use of this (in the code for an actual app) I stumble on these inconsistencies, and thus I think they are adding confusion and complexity while removing power and simplicity.

I think the problem is that the language supports overloading. If, instead of "f" and "g", both functions had the same name "f", it would be impossible for the compiler to know which one you meant in pretty much any case if it considered f(1, 2) and f((1, 2)) to be equivalent.


Now, that ambiguity does exist if you pass the tuple in as a variable, and in fact if you try this with functions that are overloaded in this way, you get a compiler error. My assumption is that the development team felt that this was an acceptable trade-off vs. the convenience of being able to pass a tuple as the argument of the function, especially since the distinction when dealing with tuple literals provides a workaround by calling f(tuple.0, tuple.1) or f((tuple.0, tuple.1)).

Thanks, I see what you mean.

(Just to illustrate what you wrote with code:

func f(x: Int, _ y: Int) { print(x + y) }
func f(t: (x: Int, y: Int)) { print(t.x + t.y) }

let aTuple = (1, 2)

f(1, 2)   // OK (1st f)
f((1, 2)) // OK (2nd f)
f(aTuple) // Error: Ambiguous use of 'f'

)

But to me, this is just one more example of how inpenetrable the supposed rules are. I'm still not sure whether any of this is intentional or not. Stranger or as strange issues have been resolved in Swift's history.


I can't see how the current behaviour is better than an "invalid redeclaration of 'f'"-error.

A compiler error on the redeclaration does seem like an acceptable solution, particularly since I'm still trying to figure out why you'd want to have a tuple as a single argument of a function in the first place.

Yes, these nested-parenthesis-examples are just examples of the following rule from the Swift iBook:

The parenthesis around a tuple pattern that contains a single element have no effect. The pattern matches values of that single element's type.


And it is partly because of this rule that I think that B in my original example should compile, since the argument list in the call

f((1, 2)) // B

is ((1, 2)) and if seen as a tuple pattern it would reduce by the above quoted rule to (1, 2), thus the call would be equivalent to f(1, 2).

If parameter lists and argument lists are tuple patterns following that rule, then B would compile. Simple as that. The following example suggests that at least parameter lists are indeed tuple patterns following the rule:

let foo = { (t: ((((Int, Int))))) -> Void in print(t) }
func bar(t: ((((x: Int, y: Int))))) { print(t) }
func baz(x: Int, y: Int) { print(x, y) }
print(foo.dynamicType) // Prints (Int, Int) -> ()
print(bar.dynamicType) // Prints (Int, Int) -> ()
print(baz.dynamicType) // Prints (Int, Int) -> ()


But since B is not compiling, argument lists (contrary to parameter lists) seems to either not be tuple patterns or they are tuple pattterns with some peculiar exception to the above quoted rule. I find both possibilites equally strange.

Argument lists are definitely not just tuple patterns, because they allow single element tuples, etc. Otherwise f(1) would become f 1 and not compile. The same is true of enum cases. Note that I'm not arguing either way on any of your examples, just trying to work out why it works as it currently does. It would certainly be simpler and more consistent if single element tuples existed and argument lists and enum cases and tuples all had identical rules, but there are probably some downsides as well. For example, single element (named?) tuples were explicitly removed because people were getting confused (see one of the early beta changelogs). This change briefly broke using a label for a single piece of associated data in an enum case, which I reported at the time and was fixed.

I meant that the "redeclaration of f" error should be reported for whatever version of the two, whichever is the one that's being redeclared. Like this:

func f(x: Int, _ y: Int) { print(x + y) }
func f(t: (x: Int, y: Int)) { print(t.x + t.y) } // <-- Invalid redeclaration of 'f'


and similarly if they changed places:

func f(t: (x: Int, y: Int)) { print(t.x + t.y) }
func f(x: Int, _ y: Int) { print(x + y) }  // <-- Invalid redeclaration of 'f'


So I was not saying that one of them should be considered more illegal than the other, just that they could be considered "the same" (yes I know named parameter names complicates this).

And I'm pretty sure there are a few sane reasons to use the single tuple parameter form. It could be useful in initializers whose argument list type depends on some generic type parameter for example.

Yes, I wish things where simpler too, and I think they can/will become simpler eventually, as the current inconsistencies are probably bugs/transitional issues, rather than a final design. There are other strange manifistations of these inconsistencies that lead me to beleive this.


Also, I don't think you are right when you say that one-element tuples don't exist. Here's what the Swift iBook (latest prerelease) has to say about this (section about Types, subsection Tuple Types):

Void is a typealias for the empty tuple type, (). If there is only one element inside the parenthesis, the type is simply the type of that element. For example, the type of (Int) is Int, not (Int). As a result, you can name a tuple element only when the tuple has two or more elements.


The book also says the following about types (in general):

In Swift, there are two kinds of types: named types and compound types. / ... / There are two compound types: function types and tuple types.


So, reading that, it's clear that indeed there are one-element tuples (eg 1 or "abc"), and zero-element tuples (Void, ()), and two element tuples, etc.

Nowhere can I see anything about there being no one element tuples.

That you can not use names and index numbers (x.0, x.1, x.2, ...) on zero and one element tuples is something completely different.


I see no reason in the documentation why parameter and argument lists cannot or should not be seen as and follow the rules for tuple types, tuples, tuple patterns. This would of course mean that eg Int, and String can also to be considered as following the tuple rules (being a one-element tuple type, but that one-element tuple type happens to be the specific named type, according to the rules of Tuple types above).


Regarding your example of f(1) becoming f 1:

f(1) and f 1 simply means the same thing, it is the function f called with the argument 1 (since (1) is the same as 1, according to the rule also stated in the Swift iBook: "The parentheses around a tuple pattern that contains a single element have no effect").


That you cannot write f 1 in code is again a different thing, it's just because parentheses happens to be used in the syntax for function calls in Swift. Also calling a function doesn't have to look only like f(1) in Swift, it can also look like eg this:

func f(a: Int) { print(a) }
func callFunction<A, R>(fn: A -> R, withArguments args: A) -> R { return fn(args) }
infix operator ∘⨍ {}
func ∘⨍<A, R>(lhs: A -> R, rhs: A) -> R { return lhs(rhs) }
// Different ways of calling f with the argument list 1 (which is the same as (1), which is the same as ((1)) ...):
f(1)
callFunction(f, withArguments: 1)
callFunction(f, withArguments: (1))
callFunction(f, withArguments: ((1)))
// ...
f ∘⨍ 1
f ∘⨍ (1)
f ∘⨍ ((1))
// ...



EDIT:

I now realize that if parameter lists are to be considered tuple types / patterns, then by the quoted rule above, we shouldn't be able to name the parameter of a single parameter function ...

But at the same time, the grammar section of the Swift book again convinces me that parameter (list) types follow the rules of tuple types/patterns. Look at the grammar for function-types, the grammar for types and the grammar for tuple-types.

The grammar for tuple-types include inout for example.


The book also says the following in the sections about Function types and Parenthesized expressions:

Function Type

A function-type represents the type of a function, method or closure and consists of a parameter- and a return-type separated by an arrow (->):

parameter-type -> return-type

Because the parameter-type and the return-type can be a tuple type, function types support functions and methods that take multiple parameters and return multiple values.

/.../


Parenthesized Expression

/.../ Use parenthesized expressions to create tuples and to pass arguments to a function call. If there is only one value inside the parenthesized expression, the type of the parenthesized expression is the type of that value. For example, the type of the parentesized expression (1) is Int, not (Int).

/.../


So I think what the book says is pretty clear (and that implies that both A and B should compile), but the current behaviour/implementation is a mess.

Should either, both or none of these two lines compile?
 
 
Q