Rules for tuple types, parameter types, function call syntax (again)

I've mentioned this before, but it's still a mystery to me, and I think this little program might present a clearer view of the confusion.

// Two very similar functions:
func foo(a: Int, _ b: Int) { print("foo", a, b) }
func bar(a: (Int, Int)) { print("bar", a.0, a.1) }

// Type aliases for what I guess are the types of foo and bar (and a bonus type Baz):
typealias Foo = (Int, Int) -> Void
typealias Bar = ((Int, Int)) -> Void
typealias Baz = (((Int, Int))) -> Void
// According to the rules of parenthesized expressions and one element tuples (see the Swift book),
// these should all be the same type, which they are, at least according to this:
print(Foo.self == Bar.self) // true
print(Foo.self == Baz.self) // true
// Foo, Bar and Baz are all the same, which makes sense. So the types of foo
// and bar should be the same too, which they are, at least according to this:
print(foo.dynamicType) // (Int, Int) -> ()
print(bar.dynamicType) // (Int, Int) -> ()
print(foo.dynamicType == bar.dynamicType) // true

// And they also seem to be the same when called using this operator:
infix operator § {}
func §<ParamListType, ResultType>(fn: ParamListType -> ResultType, args: ParamListType) -> ResultType {
    return fn(args)
}

let x = (1, 2)

foo § (1, 2) // foo 1 2
bar § (1, 2) // bar 1 2

foo § x      // foo 1 2
bar § x      // bar 1 2

// Everything is clear and easy to understand for me up to this point.

// BUT calling them with the regular function call syntax complicates/confuses things (at least for me):

foo(x)       // foo 1 2
bar(x)       // bar 1 2

foo(1, 2)    // foo 1 2
bar(1, 2)    // Error: Extra argument in call (Why? I mean Isn't the type of the argument list here (1, 2), which is the same as eg ((1, 2))?)

foo((1, 2))  // Error: Missing argument for parameter #2 in call (Why? (foo(x) works, why shouldn't it work when x is written as a tuple literal?))
bar((1, 2))  // bar 1 2

// So, foo and bar can / cannot be called in those two ways, but it turns out
// it is really easy to invert their working vs non-working ways of calling:
let fooInBarsClothing: Bar = foo
let barInFoosClothing: Foo = bar
// This will make them behave in the way their names suggests, even
// though we saw above that Bar and Foo are the same type!
fooInBarsClothing(1, 2) // Error (but worked above)
barInFoosClothing(1, 2) // bar 1 2 (but was an error above)

fooInBarsClothing((1, 2)) // foo 1 2 (but was an error above)
barInFoosClothing((1, 2)) // Error (but worked above)


Can anyone explain and / or understand the rules behind this?

Replies

Perhaps I'm missing something, but I don't see anything here that's not covered in several of your other threads on the same issue.


Edit: That isn't quite true, because the definition of equality for the typealises and the print formatting of the dynamic type are new, but the underlying cause, that the compiler, either helpfully or unhelpfully, tries to rescue you from directly putting a bunch of extra parentheses in a function call, is identical.

I don't see why it should try to rescue me from that particular extra pair of parens when it clearly doesn't rescue me from writing eg let x = ((((1, 2)))), which compiles fine, and is the same as let x = (1, 2), according to documented rules cited below. Also, for the bar function it doesn't try to rescue me from writing an extra pair of parens, but instead it requires me to write an extra pair: bar((1, 2)), as it won't work otherwise, although this should simply be the same as bar(1, 2), if the rules were followed.


So fn((x)) should be the same as fn(x), just as eg let y = (1) is the same as let y = 1, and (((Int, Int))) is the same as (Int, Int). This is all very clearly stated in the Swift book:


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 parenthesized expression (1) is Int, not (Int).


This currently is true for all situations except(!) in the standard syntax for function calls as demonstrated by the calls to foo and bar in the above post, because there: foo(1, 2) is not the same as foo((1, 2)), ie fn(x) is not the same as fn((x)) .


For anyone that is not confused by the code in my original post, please answer these simple questions:


Does foo and bar have the same type?

If so, why can't they be called in the same way?

If not, why is foo.dynamicType equal to bar.dynamicType, and how come they have the same parameter type and the same return type?


Is Foo, Bar and Baz the same type?

If so, how come type casting to them (as demonstrated with xInYsClothing above) produces functions that must be called in different ways opposite?

If not, how come Foo.self is equal to Bar.self and Baz.self?

Sure, seems all the same as your previous threads. The parentheses that surround in a function call are not treated by the compiler as tuple parentheses. I would guess that they're designed to catch errors in understanding, like the “'is' test is always true” warning and similar, but it could also be completely unintended.


As for your final questions, they are clearly distinguished by the compiler, so the answer must be that the definition of equality simply doesn't distinguish between them. Much like

let precomposede = "\u{E9}"
let decomposede = "e" + "\u{301}"
precomposede == decomposede // true
precomposede.unicodeScalars[precomposede.unicodeScalars.startIndex] // 233
decomposede.unicodeScalars[decomposede.unicodeScalars.startIndex]   // 101

Does foo and bar have the same type?

You need to defined `the same type` for function types. As you see, function call notations needs to be different between two functions. taking that into considering, they are not `the same type`. Other than that, two functions show exactly the same behaviors, let's call it as `compatible function type` here.

If so, why can't they be called in the same way?

Function types can contain compiler hinting for function calls in addition to the compatibility info.


Is Foo, Bar and Baz the same type?

Bar and Baz can be considered as exactly the same type, but Foo and Bar (or Foo and Baz, of course) are not. They are all compatible, but not `the same`.

If not, how come Foo.self is equal to Bar.self and Baz.self?

Because equality on function types are defined based on the `compatibility`, as jawbroken tried to explain in his reply, equality operators return true for things which are not exactly the same from other point of view.

You can find another compiler hinting in the following example.

typealias Hoge = (Int, b: Int)->Void
func hoge(a: Int, b: Int) { print("hoge", a, b) }
hoge(1, b: 2)
var fooFunc: Foo = hoge
fooFunc(1, 2)
var barFunc: Bar = hoge
barFunc((1, 2))
var hogeFunc: Hoge = hoge
hogeFunc(1, b: 2)

Foo, Bar and Hoge are all compatible, but have different compiler hintings.

Yes, the compiler is currently at odds with the above Swift book quote, particularly the bold part about passing arguments to a function call. The compiler currently breaks those rules of parenthesized expressions (of which eg tuples, tuple types and arguments to functions calls are examples).

Yes, I understand what you both are saying, and I apologize for going on and on about this.

But I still think the compiler is at odds with the above Swift book quote when it treats foo((1, 2)) as being different from foo(1, 2).


And I suspect one of the reasons for this bug is the complexity created by the many strange exceptions to the rules, like the first-parameter-being-special, one-element-tuples-can't-be-labeled, etc.


I wish and think that things like the following could be more elegantly connected:

- Parenthesized expressions

- Tuples

- Argument and parameter lists

- External and local parameter names

- Tuple element labels

- Function calls

- Pattern matching

- ...


IMHO, Swift currently leads the user to believe that there is a simple unifying concept that brings all those things together and make them easy and powerful to work with, because to some extent that really is the case (which is great!).

But then these corner cases and exceptions (that are hard to remember / understand the reasons for) comes in and breaks the system (for no apparent unavoidable reasons).


To me, this simple-and-powerful-unifyingly-reused-tupleness-concept is one of the two most important and potentially wonderful parts of the language (the other one being the "compiler-oriented" approach to language design).


Perhaps the only thing that can stop me from (re)writing posts like this is if someone from the Swift team reassure me that they really care about and aim to improve (clean up) this fundamental part of the language. : )


EDIT: Just now on Twitter:

– In what ways are Foo different from Bar?

typealias Foo = (Int, Int) -> Void

typealias Bar = ((Int, Int)) -> Void

– The types are currently equivalent. The way calls are handled is inconsistent; we're working on it.

Thanks for sharing the newest information.

– The types are currently equivalent. The way calls are handled is inconsistent; we're working on it.

I really expect improvements around this tuple chaos including documentations.