중괄호와 괄호 사이의 스칼라의 공식적인 차이점은 무엇이며 언제 사용해야합니까?


329

괄호 ()와 중괄호 로 함수에 인수를 전달하는 것의 공식적인 차이점은 무엇입니까 {}?

ScalaProgramming에서 얻은 느낌 은 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

그러나 이러한 규칙을 더 잘 이해하려면 알아야 할 것이 더 많습니다.

Parens를 통한 컴파일 검사 향상

Spray의 저자는 라운드 검사를 권장합니다. 컴파일 검사가 증가하기 때문입니다. 이것은 스프레이와 같은 DSL에 특히 중요합니다. parens를 사용하면 컴파일러에게 단 한 줄만 주어져야한다는 것을 알리는 것입니다. 따라서 실수로 둘 이상을 제공하면 불만이 표시됩니다. 이제 중괄호의 경우에는 해당되지 않습니다. 예를 들어 어딘가에서 연산자를 잊어 버린 경우 코드가 컴파일되고 예기치 않은 결과가 발생하고 찾기 어려운 버그가 발생할 수 있습니다. 아래는 (표현이 순수하고 적어도 경고를 줄 것이기 때문에) 고안되었지만, 요점을 지적합니다.

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

첫 번째 컴파일, 두 번째는 제공합니다 error: ')' expected but integer literal found. 저자는 글을 쓰고 싶었다 1 + 2 + 3.

기본 인수를 가진 다중 매개 변수 메소드와 유사하다고 주장 할 수 있습니다. Parens를 사용할 때 매개 변수를 구분하기 위해 실수로 쉼표를 잊어 버리는 것은 불가능합니다.

다변

자세한 정보에 대해 간과되는 중요한 메모입니다. 중괄호를 사용하면 필연적으로 자세한 코드가 생깁니다. Scala 스타일 가이드 에는 중괄호를 닫는 것이 자신의 줄에 있어야한다고 명확하게 명시되어 있기 때문입니다.

… 닫는 중괄호는 함수의 마지막 행 바로 다음에 자체 행에 있습니다.

IntelliJ와 같은 많은 자동 재 포맷 프로그램이 자동으로이 재 포맷을 수행합니다. 따라서 가능하면 둥근 파렌을 사용하십시오.

접두사 표기법

접두사 표기법을 사용할 때 List(1,2,3) indexOf (2)매개 변수가 하나만 있으면 괄호를 생략하고로 쓸 수 있습니다 List(1, 2, 3) indexOf 2. 이것은 점 표기법의 경우가 아닙니다.

x + 2또는 과 같은 멀티 토큰 표현식 인 단일 매개 변수가있는 a => a % 2 == 0경우 괄호를 사용하여 표현식의 경계를 표시해야합니다.

튜플

때때로 괄호를 생략 할 수 있기 때문에 때때로 튜플에서 ((1, 2))와 같이 추가 괄호가 필요 하고 때로는에서와 같이 외부 괄호를 생략 할 수 있습니다 (1, 2). 혼동 될 수 있습니다.

함수 / 부분 함수 리터럴 case

스칼라는 함수와 부분 함수 리터럴에 대한 구문을 가지고 있습니다. 다음과 같이 보입니다 :

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

case명령문 을 사용할 수있는 유일한 다른 위치 는 matchand 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당신은 필요 중괄호. 함수와 부분 함수 리터럴을 구별하는 것이 궁금한 경우 대답은 다음과 같습니다. 문맥. 스칼라는 함수를 기대하면 얻을 수있는 함수입니다. 부분 함수가 필요한 경우 부분 함수를 얻습니다. 둘 다 예상되는 경우 모호성에 대한 오류가 발생합니다.

표현과 블록

괄호를 사용하여 하위 표현식을 만들 수 있습니다. 중괄호는 코드 블록을 만드는 데 사용할 수 있습니다 (이것은 함수 리터럴 이 아니므 로 코드 처럼 사용하도록주의하십시오). 코드 블록은 여러 명령문으로 구성되며 각 명령문은 가져 오기 명령문, 선언 또는 표현식 일 수 있습니다. 다음과 같이 진행됩니다.

{
    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

스칼라 코드를 더 간단하게 만드는 모든 방법에 대해 범위를 소개하지 않아도됩니다! 이상적인 방법은 없어야합니다 {}. 모든 것이 하나의 순수한 표현이어야합니다
samthebest

1
@andyczerwonka 나는 전적으로 동의하지만 유연성에 대해 지불하는 자연스럽고 피할 수없는 가격 (?)이며 표현력 => Scala는 너무 비싸지 않습니다. 이것이 특정 상황에 맞는 올바른 선택 일지라도 물론 다른 문제입니다.
Ashkan Kh. Nazary

안녕하세요, 당신 List{1, 2, 3}.reduceLeft(_ + _)이 유효하지 않다고 말할 때 , 당신은 그것이 구문 오류가 있다는 것을 의미합니까? 그러나 코드가 컴파일 될 수 있다는 것을 알았습니다. 코드를 여기에
calvin

List(1, 2, 3)대신 모든 예에서 사용 했습니다 List{1, 2, 3}. 아아, 스칼라의 현재 버전 (2.13)에서는 다른 오류 메시지 (예기치 않은 쉼표)와 함께 실패합니다. 아마도 원래 오류를 얻으려면 2.7 또는 2.8로 돌아 가야합니다.
다니엘 C. 소브랄

56

여기에 진행되는 몇 가지 규칙과 추론이 있습니다. 우선, Scala는 매개 변수가 함수일 때 중괄호를 유추합니다. 예를 list.map(_ * 2)들어 중괄호가 유추되면 이는 짧은 형식입니다 list.map({_ * 2}). 둘째, Scala를 사용하면 마지막 매개 변수 목록에서 괄호를 건너 뛸 수 있습니다. 해당 매개 변수 목록에 매개 변수가 하나 있고 함수 인 경우에는 다음 list.foldLeft(0)(_ + _)과 같이 작성할 수 있습니다 list.foldLeft(0) { _ + _ }(또는 list.foldLeft(0)({_ + _})명시 적으로 명시하려는 경우).

그러나 추가하면 case다른 사람이 언급 한대로 얻을, 일부 기능을 대신하는 기능, 및 스칼라하지 않습니다 부분 기능 중괄호, 그래서 추론 list.map(case x => x * 2)하지 않습니다 작동하지만 모두 list.map({case x => 2 * 2})list.map { case x => x * 2 }것입니다.


4
마지막 매개 변수 목록뿐만 아니라 예를 들어 list.foldLeft{0}{_+_}작동합니다.
Daniel C. Sobral

1
아, 나는 그것이 마지막 매개 변수 목록이라는 것을 읽었을 것이라고 확신했지만 분명히 나는 ​​틀렸다! 알아 둘만 한.
Theo

23

중괄호 및 괄호 사용을 표준화하기 위해 커뮤니티의 노력이 있습니다. 스칼라 스타일 가이드 (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
실제로 컨벤션은 둥근 괄호를 사용하는 것이며 링크는 비공식적입니다. 함수형 프로그래밍에서 모든 함수는 1 차 시민 일 뿐이므로 다르게 취급해서는 안되기 때문입니다. 둘째 마틴 오더 스키는 당신이 단지 방법 (예를 들어, 같은 운영자 중위 사용하려고합니다 말한다 +, --), 같은 NOT 일반 방법 takeWhile. 전체 삽입 표기법은 DSL 및 사용자 정의 연산자를 허용하는 것이므로 항상이 컨텍스트에서 사용해야합니다.
samthebest

17

스칼라에는 중괄호에 대해 구체적이거나 복잡한 것이 없다고 생각합니다. 스칼라에서 복잡해 보이는 사용법을 익히려면 몇 가지 간단한 사항을 명심하십시오.

  1. 중괄호는 코드 블록을 형성하며 마지막 코드 행으로 평가됩니다 (거의 모든 언어에서 수행)
  2. 원하는 경우 코드 블록으로 생성 할 수있는 기능 (규칙 1 준수)
  3. case 절을 ​​제외하고 한 줄의 코드에 중괄호를 생략 할 수 있습니다 (Scala choice)
  4. 코드 블록을 매개 변수로 사용하여 함수 호출에서 괄호를 생략 할 수 있습니다 (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

1. 모든 언어에서 실제로 적용되는 것은 아닙니다. 4. 스칼라에서는 실제로 사실이 아닙니다. 예 : def f (x : Int) = fx
aij

@ aij, 의견 주셔서 감사합니다. 1을 위해, 나는 스칼라가 그 {}행동에 제공하는 친숙 함을 제안하고있었습니다 . 정확성을 위해 문구를 업데이트했습니다. 그리고 4의 경우 ()와 같이 {}, 와의 상호 작용으로 인해 약간 까다 롭습니다 def f(x: Int): Int = f {x}. 그래서 제가 5를 차지했습니다. :)
lcn

1
나는 ()와 {}가 내용을 다르게 구문 분석한다는 점을 제외하고는 대부분 스칼라에서 상호 교환 가능한 것으로 생각합니다. 나는 일반적으로 f ({x})를 쓰지 않으므로 f {x}는 괄호를 생략하여 컬리로 대체하는 느낌이 들지 않습니다. 다른 언어에서는 실제로 parethese를 생략 할 수 있습니다. 예를 들어 fun f(x) = f xSML에서는 유효합니다.
aij

@aij 치료 f {x}같은 것은 f({x})더 나은 것 같다 않는 설명 의 생각으로 나를 위해, ()그리고 {}덜 직관적 교환. 그건 그렇고, f({x})해석은 스칼라 사양 (6.6 섹션)에 의해 다소 뒷받침됩니다 :ArgumentExprs ::= ‘(’ [Exprs] ‘)’ | ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ’)’ | [nl] BlockExp
lcn

13

함수 호출에서의 사용법과 다양한 일이 발생하는 이유를 설명 할 가치가 있다고 생각합니다. 누군가 이미 말했듯이 중괄호는 코드 블록을 정의합니다.이 코드는 표현식이기도하므로 표현식이 필요한 곳에 놓을 수 있으며 평가됩니다. 평가 될 때 그 문장이 실행되고 last의 진술 값은 전체 블록 평가의 결과입니다 (루비에서와 비슷 함).

우리는 다음과 같은 일을 할 수 있습니다.

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에 전달되고 "foo 입력"이 인쇄되고 마지막으로 "4"가 인쇄됩니다 .

이것은 조금 추한 것처럼 보이며 Scala는이 경우 괄호를 건너 뛸 수 있으므로 다음과 같이 쓸 수 있습니다.

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

또는

foo { x => println(x) }

그것은 훨씬 더 멋지게 보이고 이전과 같습니다. 여기서도 여전히 코드 블록이 먼저 평가되고 평가 결과 (x => println (x))가 foo에 인수로 전달됩니다.


1
나 뿐이야 하지만 실제로의 명시 적 특성을 선호합니다 foo({ x => println(x) }). 어쩌면 나는 내 방식에 너무 갇혀있다.
dade

7

을 사용 case하고 있으므로 부분 함수를 정의하고 있으며 부분 함수에는 중괄호가 필요합니다.


1
이 예제에 대한 답변뿐만 아니라 일반적인 답변을 요청했습니다.
Marc-François

5

Parens를 통한 컴파일 검사 향상

Spray의 저자는 둥근 괄호가 컴파일 검사를 증가시킬 것을 권장합니다. 이것은 스프레이와 같은 DSL에 특히 중요합니다. parens를 사용하면 컴파일러에게 단 한 줄만 주어져야한다고 알려주므로 실수로 두 개 이상 줄 경우 불평 할 것입니다. 예를 들어 코드가 컴파일되는 곳의 연산자를 잊어 버리면 예기치 않은 결과가 발생하고 찾기 어려운 버그가 발생할 수 있습니다. 아래는 (표현이 순수하고 적어도 경고를 줄 것이기 때문에) 고안되었습니다.

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

첫 번째는 컴파일하고 두 번째는 error: ')' expected but integer literal found.작성자가 쓰기를 원했습니다 1 + 2 + 3.

기본 인수를 가진 다중 매개 변수 메소드와 유사하다고 주장 할 수 있습니다. Parens를 사용할 때 매개 변수를 구분하기 위해 실수로 쉼표를 잊어 버리는 것은 불가능합니다.

다변

자세한 정보에 대해 간과되는 중요한 메모입니다. 중괄호를 사용하면 필연적으로 장황한 코드가 생깁니다. scala 스타일 가이드는 중괄호를 닫는 것이 자신의 줄에 있어야한다고 분명히 명시하고 있기 때문입니다. http://docs.scala-lang.org/style/declarations.html "... 닫는 중괄호 함수의 마지막 행 바로 다음에 자체 행에 있습니다. " Intellij와 같은 많은 자동 재 포맷 프로그램이 자동으로이 재 포맷을 수행합니다. 따라서 가능하면 둥근 파렌을 사용하십시오. 예 List(1, 2, 3).reduceLeft{_ + _}:

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

-2

중괄호를 사용하면 세미콜론이 유도되고 괄호는 그렇지 않습니다. 고려 takeWhile가 일부 기능을 기대하기 때문에, 전용 기능을 {case xxx => ??? }대신하는 경우 식 괄호의 정의가 유효하다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.