Использование закрывающего затвора в цикле for-in

Я использую функцию map() для массива в цикле for-in следующим образом:

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

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

который создает ошибку компиляции:

Использование неразрешенного идентификатора "удваивается"

Однако, если я поставлю скобки для функции map(), она отлично работает. то есть.

for doubled in numbers.map ({ $0 * 2 })
{
    print(doubled)
}

Мой вопрос заключается в том, почему компилятор не будет дифференцировать блок кода трейлинг-функции и цикла, если это вызывает проблему?

Ответ 1

Это связано с тем, что будет неопределенность относительно контекста, в котором должна работать трейлинг-функция. Альтернативный синтаксис, который работает:

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

for doubled in (numbers.map { $0 * 2 }) // All good :)
{
    print(doubled)
}

Я бы сказал, это вероятно, потому что оператор "in" имеет более высокий приоритет, чем конечные функции.

Это зависит от того, что вы считаете более читаемым.

Ответ 2

Тройная грамматика закрытия генерирует известную двусмысленность между телом оператора (в вашем случае, этот цикл for, но он применяется и к другим операторам) и к телу закрывающего закрытия. Хотя технически это возможно решить, разработчики компилятора решили запретить трейлинг-синтаксис закрытия в управлении частями различных операторов высокого уровня:

Хотя можно было бы сказать, что в некоторых случаях предполагается, выполняя произвольный просмотр или выполняя проверку типов при разборе, эти подходы имеют существенные последствия для архитектуры для компилятора. Таким образом, мы решили сохранить синтаксический анализатор простым и запретить это.

Чтобы понять природу конфликта, рассмотрите этот пример:

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

Парсер должен сделать выбор в отношении блока code 1. Он может использовать его как замыкающее закрытие expr и обрабатывать code 2 как тело цикла for или использовать code 1 как тело цикла for, обрабатывая code 2 как независимая группа операторов, заключенная в фигурные скобки - классический конфликт смены-уменьшить.

Решение таких конфликтов очень дорого. По сути, вашему парсеру необходимо продолжить поиск большего количества токенов, пока не будет понятна только одна интерпретация, или синтаксический анализатор исчерпает токены (в этом случае он принимает произвольное решение так или иначе, что требует от программистов устранения неоднозначности их программы, когда этот выбор не то, что они хотели).

Добавление скобок устраняет двусмысленность. Предполагалось, что предложения устраняют неоднозначность, добавляя обязательное ключевое слово, чтобы отделить контрольную часть цикла от его тела, то есть

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

Добавление дополнительного ключевого слова do "прикрепляет" блок слева, если он есть, к выражению и делает блок справа телом оператора цикла. Этот подход имеет большой недостаток, поскольку он является нарушением изменений. Вот почему это предложение было отклонено.

Ответ 3

Синтаксис неоднозначен (см. ответ dasblinkenlight). Для альтернативного синтаксиса:

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

numbers.map { $0 * 2 }.forEach {
    print(doubled)
}

или

let numbers = [2, 4, 6, 8, 10]
let doubledNumbers = numbers.map { $0 * 2 }

for doubled in doubledNumbers {
    print(doubled)
}