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

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

Чувство, которое я получил от программирования в 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 }

=> штраф.

Ответы [ 8 ]

346 голосов
/ 08 декабря 2010

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

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

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

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

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

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

Авторы 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) }

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

55 голосов
/ 08 декабря 2010

Здесь происходит несколько различных правил и выводов: во-первых, 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 } будут.

23 голосов
/ 08 декабря 2010

Сообщество пытается стандартизировать использование скобок и скобок, см. Руководство по стилю 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")
16 голосов
/ 28 июля 2014

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

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

Давайте объясним пару примеров по приведенным выше трем правилам:

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
13 голосов
/ 02 ноября 2012

Я думаю, что стоит объяснить их использование в вызовах функций и почему происходят разные вещи.Как уже говорилось, фигурные скобки определяют блок кода, который также является выражением, поэтому его можно поместить туда, где ожидается выражение, и оно будет оценено.При оценке его операторы выполняются, а значение последнего оператора является результатом оценки всего блока (примерно как в 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, чтослучается, что сначала печатается «Hey», затем наша функция передается в foo, печатается «Entering foo» и, наконец, печатается «4».

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

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

или

foo { x => println(x) }

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

6 голосов
/ 08 декабря 2010

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

4 голосов
/ 29 декабря 2014

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

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

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 {
  _ + _
}
0 голосов
/ 25 ноября 2017

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

...