Using trailing closure in for-in loop

前端 未结 3 1476
生来不讨喜
生来不讨喜 2021-02-20 13:42

I\'m using map() function of array in for-in loop like this:

let numbers = [2, 4, 6, 8, 10]

for doubled in numbers.map { $0 * 2 } // compile error
         


        
相关标签:
3条回答
  • 2021-02-20 14:06

    Trailing closure grammar generates a known ambiguity between a body of the statement (in your case, that's for loop, but it applies to other statements as well) and the body of trailing closure. Although it is technically possible to solve this problem, compiler designers decided to prohibit trailing closure syntax in controlling portions of various high-level statements:

    While it would be possible to tell what is intended in some cases by performing arbitrary lookahead or by performing type checking while parsing, these approaches have significant consequences for the architecture for the compiler. As such, we've opted keep the parser simple and disallow this.

    To understand the nature of the conflict, consider this example:

    for v in expr { /* code 1 */ } { /* code 2 */ }
    

    The parser needs to make a choice regarding code 1 block. It could either use it as a trailing closure of expr and treat code 2 as the body of the for loop, or use code 1 as the body of the for loop, while treating code 2 as an independent group of statements enclosed in curly braces - a classic shift-reduce conflict.

    Solving such conflicts is very expensive. Essentially, your parser needs to continue looking ahead through more tokens, until only one interpretation makes sense, or the parser runs out of tokens (in which case it makes an arbitrary decision one way or the other, requiring the programmers to disambiguate their program when that choice is not what they wanted).

    Adding parentheses removes the ambiguity. Proposals were considered to remove the ambiguity by adding a mandatory keyword to separate the control portion of the loop from its body, i.e.

    // The syntax of rejected proposal
    for doubled in numbers.map { $0 * 2 } do {
        print(doubled) //                 ^^
    }
    

    Adding an extra do keyword "attaches" the block on the left, if any, to the expression, and makes the block on the right the body of the loop statement. This approach has a major drawback, because it is a breaking change. That is why this proposal has been rejected.

    0 讨论(0)
  • 2021-02-20 14:19

    The syntax is ambiguous (see dasblinkenlight's answer). For an alternative syntax:

    let numbers = [2, 4, 6, 8, 10]
    
    numbers.map { $0 * 2 }.forEach {
        print(doubled)
    }
    

    or

    let numbers = [2, 4, 6, 8, 10]
    let doubledNumbers = numbers.map { $0 * 2 }
    
    for doubled in doubledNumbers {
        print(doubled)
    }
    
    0 讨论(0)
  • 2021-02-20 14:28

    This is because there would be ambiguity as to the context in which the trailing function should operate. An alternative syntax which works is:

    let numbers = [2, 4, 6, 8, 10]
    
    for doubled in (numbers.map { $0 * 2 }) // All good :)
    {
        print(doubled)
    }
    

    I would say this is likely because the 'in' operator has a higher precedence than trailing functions.

    It's up to you which you think is more readable.

    0 讨论(0)
提交回复
热议问题