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

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

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

=> хорошо.

Ответ 1

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

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

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) }

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

Ответ 2

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

Существует стремление сообщества стандартизировать использование фигурных скобок и круглых скобок, см. 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")

Ответ 4

Я не думаю, что в 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

Ответ 5

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

Ответ 6

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

Ответ 7

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

Авторы 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 {
  _ + _
}

Ответ 8

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