Single-Element Parethesized Expressions/Tuples VS Common Use of Parentheses

Single-element parethesized expressions/tuples vs common use of parentheses

From Types in the Swift book:

If there is only one element inside the parentheses, the type is
simply the type of that element. For example, the type of (Int) is
Int, not (Int).

So the type of (2) or (2+4) is simply Int, and the * in (2+4)*5 is
just integer multiplication.

Why parenthesis are needed in tuples?

Because sometimes without the parentheses the expression would be ambiguous: without them, you are making a function call with three arguments. Since some functions do need to take three arguments, a different representation is needed to express a single three-element tuple argument. As it happens, type() can be called with one or three arguments. In the latter case the first argument must be a classname, so it complains when it sees an integer.

Tokenizing a string that could be a tuple or something else

The lexer doesn't figure out whether a ( is part of a tuple. It just recognises the (. That's all it has to do.

Deciding what a particular symbol means is the job of the parser. The parser will use a grammatical description of the language to distinguish between tuples, parenthesised expressions, parameter lists, and all the other possible meanings of parentheses.

The precise grammar will, of course, depend on the syntax of the language, but a simple example might be: (loosely adapted from Python, but missing lots of syntax)

expr  : term             /* Additive operators, lowest precedence */
| expr '+' term
| expr '-' term
term : factor /* Multiplicative operators */
| term '*' factor
| term '/' factor
factor: postfix /* Unary prefix operators */
| '-' factor
| '+' factor
post : unit /* Postfix operators: call and subscript */
| post '(' opt_expr_list ')'
| postfix '[' expr ']'
unit : CONSTANT /* Values */
| IDENTIFIER
| tuple
| '(' expr ')' /* Parenthesised expression */
tuple : '(' ')' /* Empty tuple */
| '(' expr ',' ')' /* Single-element tuple eg. (1, ) */
| '(' expr ',' expr_list opt_comma ')'
/* Two or more elements */
opt_comma
: %empty
| ','
expr_list
: expr
| expr_list ',' expr
opt_expr_list
: %empty
| expr_list

Note that the syntax has been carefully crafted to allow tuples to be distinguished from parenthesized expressions. The convention that is used is that tuples may be written with a , after the last element unless the tuple has exactly one element, in which case the comma the mandatory. This slightly requires breaking the syntax for tuples into three productions, but it is not particular complicated.

Note also that it wasn't necessary to jump through this particular hoop when describing the grammar for function calls. It is impossible to confuse the (3) in sin(3) with a parenthesised expression, since parenthesised expressions (like other values) cannot immediately follow an expression without some sort of operator.

Why does Swift accept code that should return a tuple, but instead returns a string?

There's special treatment for tuples of 0 or 1 element. As far as the type system is concerned:

  • () is equivalent to Void()
  • (T) is equivalent to T

Weird unwraping of tuples in Swift

(Int, Int) is a tuple type (where the parantheses are part of its type), just as ((Int, Int)) is the same tuple type, but wrapped in an extra pair of (redundant) parantheses, just as (((Int, Int))) is the same tuple type as the two previous ones, but wrapped in two sets of (redundant) parantheses.

var a: (((Int, Int)))
print(type(of: a)) // (Int, Int)

Additional parantheses only come into effect if you start combining different types on a nested level, e.g.

var a: ((Int, Int), Int)
print(type(of: a)) // ((Int, Int), Int)`.

Now, why do the first map closure fail, whereas the 2nd do not?

When use trailing closures, you may either

  1. Use shorthand argument name ($0, ...), or
  2. Use explicitly named (or explicitly name-omitted, '_') parameters.

Both your examples attempt to use named parameters, but only the 2nd example follows the rules for using name parameters: namely, that the parameter names must be supplied (supplying parameter types and closure return type is optional, but in some cases needed due to compiler type inference limitations).

Study the following examples:

/* all good */
arr.map { (a) in
2*a /* ^-- explicitly name parameter */
}

// or
arr.map { a in
2*a /* ^-- explicitly name parameter */
}

/* additional parantheses:
error: unnamed parameters must be written with the empty name '_'

the closure now believes we want to supply parameter name as well as
an explicit type annotation for this parameter */
arr.map { ((a)) in
2*a /* ^-- compiler believes this is now a _type_, and prior to realizing
that it is not, prompts us for the error that we have not
supplied a parameter name/explicly omitted one with '_' */
}

/* additional parantheses:
error: use of undeclared type 'a' */
arr.map { (_: (a)) in
1 /* ^-- type omitted: compiler now realize that 'a' is not a type */
}

/* fixed: all good again! */
arr.map { (_: (Int)) in
1
}

In your first example you wrap your tuple in the attempted naming of the tuple elements (in the closure's .... in part) in paranthesis (just as the errors shown above), which means Swift believes it to be a type (type (x, y)), in which case the compiler requires including an internal parameter name or explicitly omitting one (using _). Only when you supply a parameter name will the compiler realize that x and y are not valid types.

In your 2nd example you simply directly bind the closures two tuple members to the internal parameter names x and y, choosing not to explicitly type annotate these parameters (which is ok).

Why do tuples in a list comprehension need parentheses?

Python's grammar is LL(1), meaning that it only looks ahead one symbol when parsing.

[(v1, v2) for v1 in myList1 for v2 in myList2]

Here, the parser sees something like this.

[ # An opening bracket; must be some kind of list
[( # Okay, so a list containing some value in parentheses
[(v1
[(v1,
[(v1, v2
[(v1, v2)
[(v1, v2) for # Alright, list comprehension

However, without the parentheses, it has to make a decision earlier on.

[v1, v2 for v1 in myList1 for v2 in myList2]

[ # List-ish thing
[v1 # List containing a value; alright
[v1, # List containing at least two values
[v1, v2 # Here's the second value
[v1, v2 for # Wait, what?

A parser which backtracks tends to be notoriously slow, so LL(1) parsers do not backtrack. Thus, the ambiguous syntax is forbidden.

Enclose right hand side of expression in Swift language

@MartinR mentioned has mentioned link to another question where it is confirmed Bool and (Bool) are same. Thanks martin for help.



Related Topics



Leave a reply



Submit