Яка формальна відмінність Scala між дужками та дужками, і коли їх слід використовувати?


329

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

Відчуття, яке я отримав із книги « Програмування в Скалі», полягає в тому, що Scala досить гнучка, і я повинен використовувати той, який мені найбільше подобається, але я вважаю, що деякі випадки складаються, а інші - ні.

Наприклад (мається на увазі як приклад; я вдячний за будь-яку відповідь, що обговорює загальну справу, а не лише цей конкретний приклад):

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

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

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

=> штрафу.

Відповіді:


365

Я спробував одного разу написати про це, але врешті-решт я відмовився, оскільки правила дещо розсіяні. В основному, вам доведеться розвісити це.

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

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

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

Однак вам потрібно знати більше, щоб краще зрозуміти ці правила.

Збільшена перевірка компіляції з використанням паролів

Автори Spray рекомендують круглі парени, оскільки вони надають підвищену перевірку компіляції. Це особливо важливо для таких DSL, як Spray. Використовуючи пароні, ви говорите компілятору, що йому слід надати лише один рядок; тому, якщо ви випадково дасте йому два і більше, він скаржиться. Зараз це не так з фігурними дужками - якщо, наприклад, ви десь забули оператора, ваш код скомпілюватиметься, і ви отримаєте несподівані результати та, можливо, дуже важку помилку. Нижче надумано (оскільки вирази чисті і, принаймні, будуть попереджати), але зазначає:

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

Тож, сподіваюся, це допомагає.


53
Ось чому люди стверджують, що Скала є складним. Я б назвав себе ентузіастом Скали.
andyczerwonka

Не потрібно вводити область для кожного методу, я думаю, що робить код Scala простішим! В ідеалі жодним методом не слід користуватися {}- все повинно бути єдиним чистим виразом
samthebest

1
@andyczerwonka Я повністю згоден, але це природна і неминуча ціна (?), яку ви платите за гнучкість і виразність = = Scala не завищена. Однак це правильний вибір для будь-якої конкретної ситуації, звичайно, інша справа.
Ашкан Х. Назарій

Привіт, якщо ви скажете List{1, 2, 3}.reduceLeft(_ + _), що невірно, ви маєте на увазі помилку синтаксису? Але я знаходжу, що код може скласти. Я ставлю тут
calvin

Ви використовували List(1, 2, 3)в усіх прикладах замість List{1, 2, 3}. На жаль, у поточній версії Scala (2.13) це не вдається з іншим повідомленням про помилку (несподівана кома). Вам, мабуть, доведеться повернутися до 2.7 або 2.8, щоб отримати оригінальну помилку, ймовірно.
Даніель К. Собрал

56

Тут відбувається декілька різних правил і висновків: насамперед, Скала підводить дужки, коли параметр є функцією, наприклад, у 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 }буде.


4
Не тільки з останнього списку параметрів. Наприклад, list.foldLeft{0}{_+_}працює.
Даніель К. Собрал

1
Ах, я був впевнений, що прочитав, що це лише останній список параметрів, але явно помилявся! Добре знати.
Тео

23

Спільнота намагається стандартизувати використання дужок і дужок, див. Посібник зі стилів 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")

18
Насправді умовою є використання круглих брекетів, це посилання неофіційне. Це відбувається тому, що у функціональному програмуванні всі функції є лише громадянами першого порядку, і тому НЕ слід по-різному ставитися до них. По-друге, Мартин Одерський каже, що ви повинні намагатися використовувати інфікс лише для методів, таких як оператор (наприклад +, --), а не звичайних методів, таких як takeWhile. Вся суть позначення інфіксації полягає в тому, щоб дозволити DSL та користувальницькі оператори, тому слід використовувати її в цьому контексті не весь час.
samthebest

17

Я не думаю, що у Scala є щось особливе або складне щодо фігурних брекетів. Щоб освоїти начебто складне використання їх у Scala, просто пам’ятайте про кілька простих речей:

  1. фігурні дужки утворюють блок коду, який оцінюється до останнього рядка коду (майже всі мови роблять це)
  2. за бажанням може бути створена функція за допомогою блоку коду (слідує правилу 1)
  3. фігурні фігурні дужки можуть бути опущені для однорядкового коду, за винятком пункту регістру (вибір 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

1. насправді не відповідає всім мовам. 4. насправді не відповідає дійсності в Scala. Напр .: def f (x: Int) = fx
aij

@aij, дякую за коментар. Для 1 я пропонував знайомість, яку Scala забезпечує для {}поведінки. Я оновив формулювання для точності. І для 4-х це трохи складніше через взаємодію між ()і {}, як def f(x: Int): Int = f {x}працює, і саме тому у мене був 5-й. :)
lcn

1
Я схильний вважати () і {} переважно взаємозамінними у Scala, за винятком того, що він розбирає вміст по-різному. Зазвичай я не пишу f ({x}), тому f {x} не відчуває настільки пропускання дужок, як заміна їх на фігурні. Інші мови насправді дозволяють опускати паретези, наприклад, fun f(x) = f xце дійсно в SML.
аїй

@aij, поводження, f {x}як f({x})видається, є для мене кращим поясненням , оскільки мислення ()та {}взаємозамінність менш інтуїтивно зрозумілі. До речі, f({x})інтерпретація дещо підкріплена специфікою Scala (розділ 6.6):ArgumentExprs ::= ‘(’ [Exprs] ‘)’ | ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ’)’ | [nl] BlockExp
lcn

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, тому ми можемо використовувати функцію literal і передати її foo:

foo( x => println(x) )

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

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

Тут відбувається те, що код всередині {} оцінюється, а значення функції повертається як значення оцінки блоку, і це значення передається foo. Це семантично те саме, що і попередній виклик.

Але ми можемо додати щось більше:

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

Тепер наш код коду містить два висловлювання, і оскільки він оцінюється перед тим, як виконати foo, що відбувається, це спочатку надрукується "Hey", потім наша функція передається foo, "Друкується foo" друкується і нарешті "4" друкується .

Це виглядає трохи некрасиво, і Scala дозволяє нам пропустити дужки в цьому випадку, щоб ми могли написати:

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

або

foo { x => println(x) }

Це виглядає набагато приємніше і рівнозначно колишнім. Тут все-таки блок коду оцінюється спочатку і результат оцінки (який є x => println (x)) передається як аргумент foo.


1
Хіба тільки я. але я насправді віддаю перевагу явній природі foo({ x => println(x) }). Можливо, я занадто застряг у своїх дорогах ...
мертвий

7

Оскільки ви використовуєте case, ви визначаєте часткову функцію, а часткові функції потребують фігурних дужок.


1
Я попросив відповідь взагалі, а не лише відповідь на цей приклад.
Марк-Франсуа

5

Збільшена перевірка компіляції з використанням паролів

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

-2

За допомогою дужок ви отримали крапку з комою для вас, а круглі дужки - ні. Розглянемо takeWhileфункцію, оскільки вона очікує часткової функції, а лише {case xxx => ??? }виразні дужки навколо вираження регістру є допустимим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.