What is parentheses around a function name for?

I don't understand this first one of three. Do you?

func function(label: String) { }
(function)(label: "") // Good
(function as (_) -> _)("") // Good
(function as (_) -> _)(label: "") // Bad – Extraneous argument label 'label:' in call

More nesting does not change the compilation behavior.

(((function)))(label: "") // Good
(function as (_) -> _)

casts your function into a closure, and closures cannot be called with labelled parameters — only functions can.

4 Likes

I only don't understand the first one. The others are only for reference.

1 Like
(function)(label: "")
(((function)))(label: "")

are identical to the way you would normally call a function:

function(label: "") 

Swift's syntax rules dictate that in this case adding parentheses doesn't do anything, and ignores them. Does that clarify at all?

4 Likes

In addition to what @mattcurtis wrote: any expression that evaluates to a function can be followed by parentheses and is interpreted as a function call.

For example, you can have an array of functions, then calling one of them would be as follows:

let a = [0, 1, 2].map { i in
	return { print(i) } // creates a function that prints `i`
}

a[2]() // prints 2
(a[2])() // does the same
1 Like

For where I am right now, I'm feeling that what I'm seeing does the opposite of clarifying. :slightly_frowning_face: I thought that parentheses would "turn a function into a closure". I thought that parentheses were equivalent to a shorthand for an immediately-invoked closure expression, for single-line expressions.

Methods demonstrate how weird this gets, better, due to their two syntax flavor options. These all compile, and are different spellings for the same thing…

struct S {
  func function(label: String) { }
}
S().function(label:)("")
;
{ S().function } ()("")
;
{ S.function(.init()) } ()("")

But as shown with the original example, parentheses don't work how I thought they did.

All of these error out with Missing argument label 'label:' in call. The parentheses don't do anything—definitely not what I think it is reasonable to expect—so I don't understand why they're permitted.

(S().function)("")
S.function(.init())("")
(S.function(.init()))("")

Curried-style syntax can't even be used when cast to closure inline:

(S().function as (_) -> _)("") // Compiles.
(S.function(.init()) as (_) -> _)("") // Error — Cannot convert value of type '(Swift.String) -> ()' to type '(Swift.String) -> ()' in coercion

…without a force cast, anyway.

(S.function(.init()) as! (_) -> Void)("") // This does work at runtime.

These are all the same

function (label: "")
(function) (label: "")
((function)) (label: "")

Because

call-expression: function-expression argument-expression

function-expression: '(' function-expression ')'
function-expression: function

func test () {
    fubar (u: 0)
    (fubar) (u: 1)
    ((fubar)) (u: 2)
    
    let f = fubar
    let g = (fubar)
    let h = ((fubar))
    f (10)
    g (11)
    h (12)
}

func fubar (u: Int) {
    print (#function, u)
}
3 Likes

Pulling back from closures specifically for a second: parentheses in Swift, and in many other languages, are used for grouping elements together:

  1. In an expression, parentheses group part of a sub-expression together to indicate precedence — e.g., x + y / 2 vs. (x + y) / 2
  2. In an expression, parentheses can also group multiple separate sub-expressions together into a tuple — e.g., (x, y)
  3. When calling a function/closure/method, parentheses are used to group the arguments to the function together

Parentheses of form (1) above are used for parsing, but don't change the meaning of code outside of the precedence of operations. For example:

let x: Int = 5
let y: Int = (5)
let z: Int = ((5))

x, y, and z are all equivalent, and identical: the parentheses are grouping a sub-expression, but that sub-expression is just a singular value. (Why is this valid? Think: (x + y) / 2; getting rid of / 2 yields (x + y); getting rid of + y yields (x))

Here, x, y, and z are Ints, but they could easily all be values of a different type:

let x: () -> Void = function
let y: () -> Void = (function)
let z: () -> Void = ((function))

In this case, x, y, and z are all function types which take no arguments and return nothing; wrapping up the value in parentheses doesn't do anything here either.

In order to call x, y, and z, here, you use a separate pair of parentheses which go after the variable name, passing in the arguments internally — here I'm passing no arguments, so the parentheses are empty:

x()
y()
z()

This turns the references of x, y, and z, into function calls, and are parens of form (3) above.

Putting the definitions of x, y, and z together with their calls, you get the fact that

function()
(function)()
((function))()

are then also all equivalent. The parentheses around function are grouping function itself (doing nothing), and the parentheses that go after are grouping the parameters.

5 Likes

That looks useful. What does it mean and where did you get it?


Further testing showed me that it doesn't just have to be one function in the parentheses. Ternary expressions yield the same behavior. What else is like this? (The newish switch and if/else expressions don't exhibit this behavior.)

( true ? S1.f1(.init())
  : true ? S1.f2(.init())
  : S2.f1(.init())
) (label: 0)
struct S1 {
  func f1(label: Int) { }
  func f2(label: Int) { }
}

struct S2 {
  func f1(label: Int) { }
}

It only happens with curried syntax. With "normal" syntax, the functions are turned into type-system-representable closures.

( true ? S1().f1
  : true ? S1().f2
  : S2().f1
) (label: 0) // Extraneous argument label 'label:' in call

…which makes this seem even brokener than I had thought it was:

(true ? S1().f1 : S2().f1)(0) // Compiles
(S1().f1)(0) // Missing argument label 'label:' in call

Well, this is unexpected.

struct S {
  func f(label: Int, moreLabel: String) { }
}
let s = S()
let closureWithLabels = true ? S.f(s) : S.f(s)
closureWithLabels(label: 0, moreLabel: "") // Compiles.

Or should I have expected it? I don't know that I understand things anymore.

1 Like

that looks like a bug to me

3 Likes

To understand this behavior, you need to understand the difference between a function and an instance of a function type.

Since SE-0111, instances of a function type can't have argument labels. (At least, they aren't supposed to.) Functions, on the other hand, can have argument labels. You can convert a function to an instance of a function type, and in some cases Swift will automatically convert it, but it will lose the argument names.

When you compile the following code:

(function)(label: "")

function is resolved to a function with argument labels, and the parenthesized expression evaluates to whatever the expression inside the parentheses evaluates to, so it needs to be called with argument labels.

For this code:

(function as (_) -> _)("")

function is resolved to a function with argument labels, but the cast converts it to an instance of a function type, which does not have argument labels.


In the first statement here, S1().f1 and S2().f1 are being converted from functions to instances of a function type. This is because the ternary conditional operator requires its operands to be instances of a type. In fact, most places in Swift that accept expressions require instances of a type, and will automatically convert functions to make them work. This might be why you expected parentheses to convert a function to an instance of a function type.


I agree with taylorswift — it looks like this is a bug. It seems that when SE-0111 was implemented, they forgot to get rid of argument labels on unapplied instance methods.

4 Likes

Just like PEMDAS from algebra class back in school, programming languages always follow an order of operations. And also just like PEMDAS, parentheses tend to have a higher precedence than most (or all) other things. This is the case here:

(some expression that resolves to a function)()

will become

theFunction()

and will then be called.

1 Like

That's exactly it. I was under the impression that as soon as you isolated what you're calling a "function", in any way, it got SE-0111'd. Even the alleged bug can't save the labels from tupling, for example.

struct S {
  func f(label: String) { }
}
(S().f, ()).0("") // Compiles.
(S.f(.init()), ()).0(label: "") // Extraneous argument label 'label:' in call

I thought that the only way to involve labels was to use ( or [ immediately after a function name. The alleged bug is helping me to understand that the compiler has access to "closures with labels" that programmers normally do not, which, like everything else, are unaffected by enclosing parentheses:

// This all compiles:
let s = S()
typealias Itself<Value> = Value
(S.f(s) as Itself)(label: "")
let closureWithLabel = S.f(s) as Itself
(closureWithLabel)(label: "")

So, as for the question in the title…

While this discussion has been helpful to understand why the parentheses compile, if wrangling closures with labels into expressions is actually a bug, I don't think the parentheses are for anything*. Has anyone ever seen them used, or even thought about using them? If so, why?

(I don't know how to say this with succinct and correct verbiage, but I think all of the other single-element parenthetical groupings exist only as artifacts of being able to use parentheses for grouping multi-element sub-expressions?)

* I bet this label removal isn't for a purpose, either. But at least the casting makes an explainable change.

(s.f as Itself)("")

Happens more often in C and C++. But Swift's operator precedence, plus the kind of expressions that can evaluate to a function in Swift make the parenthesis unnecessary in majority of cases. Some examples of the opposite have been shown in this thread, but they are rather exotic.

I don't think this is true. I've been watching like a hawk with eyes in the back of its head.

1 Like

E.g. the ones with a typecast require parenthesis, no?

(f as SomeFuncType)()
1 Like

Hint: :slight_smile:

// 1.cc

#include <stdio.h>

static long fubar (const long);

int main (int argc, const char *argv []) {
   long (*f)(const long) = fubar;

   (*f) (21);
   ((*f)) (22);
   f (30);
   (f) (31);
   fubar (5);

   return 0;
}

static long fubar (const long u) {
   printf ("%s: %ld\n", __func__, u);
   return 2 * u + 1;
}

From the Terminal:

vi 1.cc
g++ 1.cc -o fubar
./fubar

If you e.g. need to access + as a function, you sometimes need to spell it (+)

let sum = (+)(1, 2)
6 Likes

This is a good use case. But as the parentheses are not optional, and "Operator cannot have keyword arguments", I think this is a different animal than "function", despite the same func keyword being involved.

prefix func +(argument parameter: Void) { } // Error.

The named function equivalent does not compile. Parentheses break leading dot syntax.

extension Int {
  static func add(l: Self, r: Self) -> Self { l + r }
}
let _: Int = .add(l: 1, r: 2) // Compiles.
let _: Int = (.add)(l: 1, r: 2) // Error: Cannot infer contextual base in reference to member 'add'