Какая формальная разница в Scala между фигурными скобками и скобками, и когда они должны использоваться?

262

Какова формальная разница между передачей аргументов функциям в круглых скобках () и в фигурных скобках {}?

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

Например (только в качестве примера, я был бы признателен за любой ответ, который обсуждает общий случай, а не только этот конкретный пример):

val tupleList = List[(String, String)]()
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 )

= > ошибка: незаконный запуск простого выражения

val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }

= > штраф.

Теги:
syntax
parentheses
braces

8 ответов

280
Лучший ответ

Я попытался написать об этом, но в конце концов я сдался, так как правила несколько размыты. В принципе, вам придется повесить его.

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

List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter

List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter

Однако, вам больше нужно знать, чтобы лучше понять эти правила.

Расширенная проверка компиляции с помощью parens

Авторы Spray рекомендуют круглые парнеры, потому что они дают расширенную проверку компиляции. Это особенно важно для DSL, таких как Spray. Используя parens, вы сообщаете компилятору, что ему должна быть предоставлена ​​только одна строка; поэтому, если вы случайно дадите ему два или более, он будет жаловаться. Теперь это не так с фигурными фигурными скобками - если, например, вы где-то забыли оператор, тогда ваш код будет компилироваться, и вы получите неожиданные результаты и, возможно, очень сложную ошибку. Ниже изобретено (так как выражения чисты и будут хотя бы давать предупреждение), но делает точку:

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

Первый компилятор, второй - error: ')' expected but integer literal found. Автор хотел написать 1 + 2 + 3.

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

Многословность

Важное часто забытое примечание о многословии. Использование фигурных скобок неизбежно приводит к подробному коду, так как руководство Scala четко указывает, что закрытие фигурных скобок должно быть в отдельной строке:

... закрывающая скобка находится на собственной линии сразу после последней строка функции.

Многие автореформаторы, как и в IntelliJ, автоматически выполнит эту переформатировку для вас. Поэтому старайтесь использовать круглые парнеры, когда сможете.

Инф. нотация

При использовании нотации infix, например List(1,2,3) indexOf (2), вы можете опустить скобки, если есть только один параметр и записать его как List(1, 2, 3) indexOf 2. Это не относится к точечной нотации.

Обратите внимание также, что если у вас есть один параметр, который является выражением с несколькими токенами, например x + 2 или a => a % 2 == 0, вам нужно использовать скобки для указания границ выражения.

кортежи

Поскольку иногда вы можете иногда пропускать скобки, иногда кортеж нуждается в дополнительной скобке, например, в ((1, 2)), а иногда внешняя скобка может быть опущена, как в (1, 2). Это может вызвать путаницу.

Функциональные/частичные литералы функции с case

Scala имеет синтаксис для литералов функций и частичных функций. Это выглядит так:

{
    case pattern if guard => statements
    case pattern => statements
}

Только в других местах, где вы можете использовать операторы case, с ключевыми словами match и catch:

object match {
    case pattern if guard => statements
    case pattern => statements
}
try {
    block
} catch {
    case pattern if guard => statements
    case pattern => statements
} finally {
    block
}

Вы не можете использовать операторы case в любом другом контексте. Итак, если вы хотите использовать case, вам нужны фигурные скобки. В случае, если вам интересно, что делает различие между функцией и литералом частичной функции, ответ таков: контекст. Если Scala ожидает функцию, вы получаете функцию. Если он ожидает частичную функцию, вы получаете частичную функцию. Если оба ожидаются, это дает ошибку о двусмысленности.

Выражения и блоки

Скобки могут использоваться для создания подвыражений. Кудрявые фигурные скобки можно использовать для создания блоков кода (это не функция буквально, поэтому остерегайтесь использовать его как один). Блок кода состоит из нескольких операторов, каждый из которых может быть оператором импорта, объявлением или выражением. Это происходит следующим образом:

{
    import stuff._
    statement ; // ; optional at the end of the line
    statement ; statement // not optional here
    var x = 0 // declaration
    while (x < 10) { x += 1 } // stuff
    (x % 5) + 1 // expression
}

( expression )

Итак, если вам нужны декларации, несколько операторов, import или что-то в этом роде, вам нужны фигурные скобки. И поскольку выражение является выражением, скобки могут появляться внутри фигурных скобок. Но интересно то, что блоки кода также являются выражениями, поэтому вы можете использовать их где угодно внутри выражения:

( { var x = 0; while (x < 10) { x += 1}; x } % 5) + 1

Итак, поскольку выражения - это выражения, а блоки кодов - выражения, все ниже верно:

1       // literal
(1)     // expression
{1}     // block of code
({1})   // expression with a block of code
{(1)}   // block of code with an expression
({(1)}) // you get the drift...

Где они не взаимозаменяемы

В принципе, вы не можете заменить {} на () или наоборот в другом месте. Например:

while (x < 10) { x += 1 }

Это не вызов метода, поэтому вы не можете написать его каким-либо другим способом. Ну, вы можете поместить фигурные скобки внутри скобки для condition, а также использовать скобки внутри фигурных скобок для блока кода:

while ({x < 10}) { (x += 1) }

Итак, я надеюсь, что это поможет.

  • 41
    Вот почему люди утверждают, что Скала сложна. И я бы назвал себя энтузиастом Scala.
  • 0
    Не нужно вводить область действия для каждого метода, я думаю, что код Scala проще! В идеале ни один метод не должен использовать {} - все должно быть одним чистым выражением
52

Существует несколько различных правил и выводов: во-первых, Scala отображает фигурные скобки, когда параметр является функцией, например. в list.map(_ * 2) выводятся фигурные скобки, это всего лишь более короткая форма list.map({_ * 2}). Во-вторых, Scala позволяет пропустить скобки в последнем списке параметров, если в списке параметров есть один параметр, и это функция, поэтому list.foldLeft(0)(_ + _) может быть записана как list.foldLeft(0) { _ + _ } (или list.foldLeft(0)({_ + _}), если вы хотите быть более явным).

Однако, если вы добавите case, вы получите, как указывали другие, частичную функцию вместо функции, а Scala не будет выводить скобки для частичных функций, поэтому list.map(case x => x * 2) не будет работать, но оба list.map({case x => 2 * 2}) и list.map { case x => x * 2 } будут.

  • 3
    Не только из последнего списка параметров. Например, list.foldLeft{0}{_+_} работает.
  • 1
    Ах, я был уверен, что прочитал, что это был только последний список параметров, но явно я ошибся! Хорошо знать.
19

Существует стремление сообщества стандартизировать использование фигурных скобок и круглых скобок, см. Scala Руководство по стилю (стр. 21): http://www.codecommit.com/scala-style-guide.pdf

Рекомендуемый синтаксис вызовов методов более высокого порядка - это всегда использовать фигурные скобки и пропустить точку:

val filtered = tupleList takeWhile { case (s1, s2) => s1 == s2 }

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

val result = myInstance.foo(5, "Hello")
  • 17
    На самом деле соглашение заключается в использовании круглых скобок, эта ссылка не является официальной. Это связано с тем, что в функциональном программировании все функции являются гражданами первого порядка и поэтому НЕ должны рассматриваться по-разному. Во-вторых, Мартин Одерски говорит, что вы должны пытаться использовать только инфикс для методов, подобных оператору (например, + , -- ), а НЕ обычных методов, таких как takeWhile . Весь смысл инфиксной нотации состоит в том, чтобы разрешить DSL и пользовательские операторы, поэтому его следует использовать в этом контексте не всегда.
13

Я думаю, что стоит объяснить их использование в вызовах функций и почему происходят разные вещи. Поскольку кто-то уже сказал, что фигурные скобки определяют блок кода, который также является выражением, поэтому можно поставить выражение, в котором ожидается выражение, и оно будет оценено. Когда оценивается, его операторы выполняются, а последнее значение оператора - результат оценки целочисленного блока (несколько как в Ruby).

Имея это, мы можем делать такие вещи, как:

2 + { 3 }             // res: Int = 5
val x = { 4 }         // res: x: Int = 4
List({1},{2},{3})     // res: List[Int] = List(1,2,3)

Последний пример - это просто вызов функции с тремя параметрами, каждый из которых сначала оценивается.

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

def foo(f: Int => Unit) = { println("Entering foo"); f(4) }

Чтобы вызвать его, нам нужно передать функцию, которая принимает один параметр типа Int, поэтому мы можем использовать функциональный литерал и передать его в foo:

foo( x => println(x) )

Теперь, как сказано ранее, мы можем использовать блок кода вместо выражения, поэтому пусть он использует его

foo({ x => println(x) })

Что происходит здесь, так это то, что код внутри {} оценивается, а значение функции возвращается как значение оценки блока, это значение затем передается в foo. Это семантически то же самое, что и предыдущий вызов.

Но мы можем добавить что-то еще:

foo({ println("Hey"); x => println(x) })

Теперь наш блок кода содержит два оператора, и поскольку он вычисляется до выполнения foo, то происходит то, что сначала печатается "Эй", затем наша функция передается в foo, "Ввод foo" печатается и, наконец, "4".

Это выглядит немного уродливо, и Scala позволяет нам пропустить скобку в этом случае, поэтому мы можем написать:

foo { println("Hey"); x => println(x) }

или

foo { x => println(x) }

Это выглядит намного приятнее и эквивалентно предыдущим. Здесь все еще блок кода оценивается первым, и результат оценки (который является x = > println (x)) передается как аргумент для foo.

  • 0
    Это только я. но я на самом деле предпочитаю явную природу foo({ x => println(x) }) . Может быть, я слишком застрял в моих путях ...
10

Я не думаю, что в Scala есть что-то особенное или сложное в фигурных скобках. Чтобы освоить кажущееся сложное их использование в Scala, просто сохраните пару простых вещей:

  • фигурные скобки образуют блок кода, который оценивает последнюю строку кода (почти все языки делают это)
  • функция, если требуется, может быть сгенерирована блоком кода (следует правило 1)
  • фигурные скобки могут быть опущены для однострочного кода, за исключением предложения case (Scala choice)
  • круглые скобки могут быть опущены при вызове функции с блоком кода в качестве параметра (Scala choice)

Объясните несколько примеров в соответствии с тремя правилами:

val tupleList = List[(String, String)]()
// doesn't compile, violates case clause requirement
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 ) 
// block of code as a partial function and parentheses omission,
// i.e. tupleList.takeWhile({ case (s1, s2) => s1 == s2 })
val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }

// curly braces omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft(_+_)
// parentheses omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft{_+_}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).reduceLeft _+_ // res1: String => String = <function1>

// curly braces omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0)(_ + _)
// parentheses omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0){_ + _}
// block of code and parentheses omission
List(1, 2, 3).foldLeft {0} {_ + _}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).foldLeft(0) _ + _
// error: ';' expected but integer literal found.
List(1, 2, 3).foldLeft 0 (_ + _)

def foo(f: Int => Unit) = { println("Entering foo"); f(4) }
// block of code that just evaluates to a value of a function, and parentheses omission
// i.e. foo({ println("Hey"); x => println(x) })
foo { println("Hey"); x => println(x) }

// parentheses omission, i.e. f({x})
def f(x: Int): Int = f {x}
// error: missing arguments for method f
def f(x: Int): Int = f x
  • 0
    1. на самом деле не верно для всех языков. 4. На самом деле это не так в Scala. Например: def f (x: Int) = fx
  • 0
    @aij, спасибо за комментарий. Для 1 я предлагал знакомство, которое Scala обеспечивает для поведения {} . Я обновил формулировку для точности. И для 4 это немного сложно из-за взаимодействия между () и {} , так как def f(x: Int): Int = f {x} работает, и именно поэтому у меня было 5-е. :)
Показать ещё 2 комментария
6

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

  • 1
    Я попросил ответ вообще, а не просто ответ для этого примера.
3

Расширенная проверка компиляции с помощью парсеров

Авторы Spray, рекомендуют, чтобы круглые парсеры увеличивали проверку компиляции. Это особенно важно для DSL, таких как Spray. Используя parens, вы сообщаете компилятору, что ему должна быть предоставлена ​​только одна строка, поэтому, если вы случайно дали ей два или более, она будет жаловаться. Теперь это не так с фигурными фигурными скобками, если, например, вы забудете оператора где-нибудь, где ваш код будет скомпилирован, вы получите неожиданные результаты и потенциально очень сложную ошибку. Ниже изобретено (так как выражения чисты и будут хотя бы давать предупреждение), но делает точку

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

Первый компилятор, второй - error: ')' expected but integer literal found., автор хотел написать 1 + 2 + 3.

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

Многословность

Важное часто забытое примечание о многословии. Использование фигурных скобок неизбежно приводит к подробному коду, поскольку руководство по стилю scala четко заявляет, что закрытие фигурных скобок должно быть в отдельной строке: http://docs.scala-lang.org/style/declarations.html "... закрывающая скобка находится на собственной линии сразу после последней строки функции". Многие автореформаторы, как и в Intellij, автоматически выполнит эту переформатировку для вас. Поэтому старайтесь использовать круглые парнеры, когда сможете. Например. List(1, 2, 3).reduceLeft{_ + _} становится:

List(1, 2, 3).reduceLeft {
  _ + _
}
-3

С фигурными скобками у вас есть точка с запятой, вызванная для вас и скобок. Рассмотрим функцию takeWhile, так как она ожидает частичную функцию, только {case xxx => ??? } является корректным определением вместо круглых скобок вокруг выражения case.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню