中括弧と括弧の間のScalaの正式な違いは何ですか?それらはいつ使用されるべきですか?


329

括弧()と中括弧で関数に引数を渡すことの形式的な違いは何{}ですか?

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 }

=>結構です。

回答:


365

私は一度これについて書こうとしましたが、ルールがやや拡散しているので結局あきらめました。基本的には、コツをつかむ必要があります。

おそらく、中括弧と括弧が同じ意味で使用できる場所に集中することをお勧めします。パラメーターをメソッド呼び出しに渡すときです。メソッドが単一のパラメーターを必要とする場合に限り、括弧を中括弧で置き換えることができます。例えば:

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

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

ただし、これらのルールをよりよく理解するために知っておく必要のあることがさらにあります。

括弧によるコンパイルチェックの増加

Sprayの作成者は、コンパイルチェックを強化するため、丸かっこをお勧めします。これは、スプレーのようなDSLにとって特に重要です。括弧を使用することで、コンパイラーに単一行のみを与えるように指示しています。したがって、誤って2つ以上与えると、文句を言います。中かっこはこれに当てはまりません。たとえば、どこかで演算子を忘れた場合、コードがコンパイルされ、予期しない結果と非常に難しいバグが見つかる可能性があります。以下は工夫されていますが(式は純粋であり、少なくとも警告を与えるため)、要点を述べます:

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

最初はコンパイル、2番目はを与えerror: ')' expected but integer literal foundます。著者は書きたかった1 + 2 + 3

デフォルトの引数を持つマルチパラメータメソッドの場合も同様です。括弧を使用する場合、パラメーターを区切るために誤ってコンマを忘れることはありません。

冗長性

冗長性に関する重要な、見落とされがちなメモ。中括弧を使用すると、Scalaスタイルガイドで中括弧を閉じる必要があると明確に記述されているため、詳細なコードが必要になります。

…右中括弧は、関数の最後の行のすぐ後に続く独自の行にあります。

IntelliJのような多くの自動再フォーマッターは、この再フォーマットを自動的に実行します。可能な場合は丸括弧を使用するようにしてください。

中置記法

中置表記法を使用する場合、List(1,2,3) indexOf (2)パラメーターが1つだけの場合は括弧を省略して、と書くことができます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ステートメントを使用できる他の場所はmatchcatchキーワードとキーワードのみです。

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
そのため、Scalaは複雑であると人々は主張しています。私は自分をScala愛好家と呼んでいます。
andyczerwonka

すべてのメソッドにスコープを導入する必要がないため、Scalaコードが単純になると思います。理想的には、メソッドを使用しないでください{}-すべてが単一の純粋な表現である必要があります
samthebest

1
@andyczerwonka私は完全に同意しますが、それはあなたが柔軟性と表現力に支払う自然で必然的な価格(?)です=> Scalaは高値ではありません。これが特定の状況で正しい選択であるかどうかはもちろん別の問題です。
Ashkan Kh。ナザリー

こんにちは、List{1, 2, 3}.reduceLeft(_ + _)無効と言うとき、構文に誤りがあるということですか?しかし、コードはコンパイルできることがわかりました。私はここ
calvin

List(1, 2, 3)すべての例で、の代わりに使用しましたList{1, 2, 3}。悲しいかな、Scalaの現在のバージョン(2.13)では、これは別のエラーメッセージ(予期しないコンマ)で失敗します。おそらく、元のエラーを取得するには、2.7または2.8に戻る必要があります。
Daniel C. Sobral

56

ここでは、いくつかの異なるルールと推論があります。まず、Scalaはパラメーターが関数である場合に中括弧を推論します。たとえばlist.map(_ * 2)、中括弧が推論されている場合、それはの短い形式ですlist.map({_ * 2})。次に、Scalaでは最後のパラメーターリストの括弧をスキップできます。そのパラメーターリストにパラメーターが1つあり、それが関数である場合、次のよう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}{_+_}動作します。
ダニエルC.ソブラル2010

1
ああ、それは最後のパラメーターリストだけだと読んだことは間違いありませんが、明らかに間違っていました。知っておくと良い。
Theo

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
実際には、規則は中括弧を使用することであり、そのリンクは非公式です。これは、関数型プログラミングではすべての関数が一次市民にすぎないため、別の扱いをするべきではないためです。第二に、Martin Oderskyは、演算子のようなメソッド(例+--)には中置記号のみを使用するようにしてくださいtakeWhile。インフィックス表記の全体のポイントはDSLとカスタムオペレーターを許可することであるため、常にこのコンテキストで使用する必要はありません。
samthebest 2014

17

Scalaの波括弧には特別なものや複雑なものはないと思います。Scalaでこれらの複雑な使用法を習得するには、いくつかの簡単なことを覚えておいてください。

  1. 中括弧はコードのブロックを形成し、コードの最終行に評価されます(ほとんどすべての言語がこれを行います)
  2. コードブロックで必要に応じて関数を生成できます(ルール1に従います)
  3. 中括弧は、case文節(Scalaの選択)を除き、1行のコードでは省略できます
  4. パラメータとしてコードブロックを使用する関数呼び出しでは、括弧を省略できます(Scalaの選択)

上記の3つのルールに従って、いくつかの例を説明しましょう。

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 2015

@aij、考えて交換可能であることは直感的ではないので、f {x}同じf({x})ように扱うことは私にとってより良い説明のようです。ところで、解釈はややScalaの仕様(セクション6.6)によって支えられています:(){}f({x})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)

最後の例は、3つのパラメーターを持つ関数呼び出しであり、それぞれが最初に評価されます。

関数呼び出しでどのように機能するかを確認するために、別の関数をパラメーターとして取る単純な関数を定義しましょう。

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

これを呼び出すには、Int型のパラメータを1つ取る関数を渡す必要があるため、関数リテラルを使用してfooに渡すことができます。

foo( x => println(x) )

さて、前に述べたように、式の代わりにコードのブロックを使用できるので、使用しましょう

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

ここで発生するのは、{}内のコードが評価され、関数値がブロック評価の値として返され、この値がfooに渡されることです。これは、意味的には前の呼び出しと同じです。

しかし、さらに何かを追加できます。

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

これで、コードブロックには2つのステートメントが含まれ、fooが実行される前に評価されるため、最初に "Hey"が出力され、次に関数がfooに渡され、 "Entering foo"が出力され、最後に "4"が出力されます。 。

これは少し見苦しいように見えますが、Scalaではこの場合の括弧を省略できるので、次のように記述できます。

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

または

foo { x => println(x) }

これは見栄えが良く、以前のものと同等です。ここでも、コードのブロックが最初に評価され、評価の結果(x => println(x))が引数としてfooに渡されます。


1
私だけですか。しかし、私は実際にはの明示的な性質を好みfoo({ x => println(x) })ます。多分私は自分のやり方で行き詰まっている...
16年

7

を使用しているためcase、部分関数を定義しており、部分関数には中括弧が必要です。


1
この例の回答だけでなく、一般的な回答も求めました。
マルク・フランソワ・

5

括弧によるコンパイルチェックの増加

Sprayの作者は、丸かっこでコンパイルチェックを強化することを推奨しています。これは、スプレーのようなDSLにとって特に重要です。括弧を使用することで、コンパイラーに単一行のみを指定するように指示しているので、誤って2行以上指定すると、文句が表示されます。中かっこではこれは当てはまりません。たとえば、コードをコンパイルする場所で演算子を忘れた場合、予期しない結果が発生し、非常に難しいバグが見つかる可能性があります。以下は工夫されています(式が純粋で少なくとも警告を出すため)が、ポイントを作ります

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

最初はコンパイルされ、2番目はerror: ')' expected but integer literal found.作成者が記述したかったものを提供します1 + 2 + 3

デフォルトの引数を持つマルチパラメータメソッドの場合も同様です。括弧を使用する場合、パラメーターを区切るために誤ってコンマを忘れることはありません。

冗長性

冗長性に関する重要な、見落とされがちなメモ。中括弧を使用すると、スカラースタイルガイドで中括弧を独立させる必要があると明確に記述されているため、詳細なコードが必要になります。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.