web-dev-qa-db-ja.com

括弧、ドット、中括弧、=(関数)などを省略できる正確なルールは何ですか?

括弧、ドット、中括弧、=(関数)などを省略(省略)できる場合の正確なルールは何ですか?

例えば、

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • serviceは私のオブジェクトです
  • def findAllPresentations: Option[List[Presentation]]
  • votesList[Vote]を返します
  • mustおよびbeは両方ともspecの関数です

なぜ行けないのですか:

(service findAllPresentations get first votes size) must be equalTo(2)

コンパイラエラーは次のとおりです。

「タイプOption [List [com.sharca.Presentation]]のRestServicesSpecTest.this.service.findAllPresentationsはパラメーターを取りません。」

パラメータを渡そうとしているのはなぜですか?メソッド呼び出しごとにドットを使用する必要があるのはなぜですか?

なぜ(service.findAllPresentations get first votes size)をequalTo(2)にする必要があるのか​​:

「見つかりません:値が最初」

それでも、(service.findAllPresentations.get.first.votes.size)の "equalTo 2でなければなりません"はequalTo 2でなければなりません。つまり、メソッドチェーンは正常に機能しますか。 -オブジェクトチェーンチェーンチェーンパラメータ。

Scalaの本とウェブサイトを調べましたが、包括的な説明が実際に見つかりません。

実際、Rob HがStack Overflowの質問で説明しているようにScalaではどの文字を省略できますか?、 「。」を省略するための有効なユースケース「オペランド演算子オペランド」スタイルの操作用であり、メソッドの連鎖用ではありませんか?

100
Antony Stubbs

クラス定義:

valまたはvarは、パラメーターをプライベートにするクラスパラメーターから省略できます。

Varまたはvalを追加すると、それがパブリックになります(つまり、メソッドアクセサーとミューテーターが生成されます)。

{}は、クラスにボディがない場合、つまり、

class EmptyClass

クラスのインスタンス化:

汎用パラメーターは、コンパイラーによって推測できる場合は省略できます。ただし、型が一致しない場合、型パラメーターは常に一致するように推測されます。したがって、タイプを指定しないと、期待したものが得られない可能性があります-つまり、

class D[T](val x:T, val y:T);

これにより、タイプエラーが発生します(Int found、expected String)

var zz = new D[String]("Hi1", 1) // type error

これは正常に機能しますが、

var z = new D("Hi1", 1)
== D{def x: Any; def y: Any}

TypeパラメーターTは、2つのAnyの中で最も一般的なスーパータイプとして推測されるためです。


関数定義:

関数がUnit(nothing)を返す場合、=は削除できます。

関数が単一のステートメントである場合、ただしステートメントが値を返す場合のみ({}記号が必要)、関数本体の=はドロップできます。

def returnAString = "Hi!"

しかし、これは機能しません:

def returnAString "Hi!" // Compile error - '=' expected but string literal found."

関数の戻り値の型は、推論できる場合は省略できます(再帰メソッドには戻り値の型を指定する必要があります)。

()は、関数が引数を取らない場合、つまり、

def endOfString {
  return "myDog".substring(2,1)
}

これは、慣例により、副作用のないメソッド用に予約されています-それについては後で詳しく説明します。

()は、名前で渡すパラメーターを定義する際に実際にドロップされるわけではありませんが、実際には意味的にまったく異なる表記法、つまり、

def myOp(passByNameString: => String)

MyOpは、名前渡しパラメータを使用すると、関数パラメータではなく、文字列(つまり、文字列を返すコードブロックになる可能性があります)を取得します。

def myOp(functionParam: () => String)

myOpは、パラメーターがゼロの関数を取り、文字列を返します。

(覚えておいてください、名前で渡すパラメータは関数にコンパイルされます;それは単に構文をより良くします。)

関数が引数を1つだけとる場合、()は関数パラメーター定義にドロップできます。例:

def myOp2(passByNameString:(Int) => String) { .. } // - You can drop the ()
def myOp2(passByNameString:Int => String) { .. }

ただし、複数の引数を取る場合は、()を含める必要があります。

def myOp2(passByNameString:(Int, String) => String) { .. }

声明:

.は、演算子表記法を使用するためにドロップできます。これは、中置演算子(引数を取るメソッドの演算子)にonlyのみ使用できます。詳細については、 ダニエルの答え を参照してください。

  • .は、後置関数リストの末尾にもドロップできます

  • ()は、後置演算子list.tail用にドロップできます。

  • ()は、次のように定義されたメソッドでは使用できません。

    def aMethod = "hi!" // Missing () on method definition
    aMethod // Works
    aMethod() // Compile error when calling method
    

この表記法は、List#tailのような副作用のないメソッドのために慣習により予約されているためです(つまり、副作用のない関数の呼び出しは、戻り値を除いて、関数に観測可能な効果がないことを意味します)。

  • ()は、単一の引数を渡すときに演算子表記のためにドロップできます

  • ()は、ステートメントの最後にない後置演算子を使用する必要がある場合があります

  • ()は、ネストされたステートメント、匿名関数の終わり、または複数のパラメーターを受け取る演算子を指定するために必要になる場合があります

関数を受け取る関数を呼び出す場合、たとえば、次のように、内部関数定義から()を省略できません。

def myOp3(paramFunc0:() => String) {
    println(paramFunc0)
}
myOp3(() => "myop3") // Works
myOp3(=> "myop3") // Doesn't work

By-nameパラメーターをとる関数を呼び出す場合、パラメーターなしの匿名関数として引数を指定することはできません。たとえば、次の場合:

def myOp2(passByNameString:Int => String) {
  println(passByNameString)
}

次のように呼び出す必要があります。

myOp("myop3")

または

myOp({
  val source = sourceProvider.source
  val p = myObject.findNameFromSource(source)
  p
})

だがしかし:

myOp(() => "myop3") // Doesn't work

IMO、ドロップの戻り型を使いすぎると、コードを再利用するのに有害になる可能性があります。コードに明示的な情報がないために可読性が低下する良い例については、仕様をご覧ください。変数の型が何であるかを実際に把握するための間接レベルの数は非常に簡単です。うまくいけば、より良いツールがこの問題を回避し、コードを簡潔に保つことができます。

(OK、より完全で簡潔な答えをまとめるための探求(何かを見逃した場合、または何か間違っている/不正確な点がある場合はコメントしてください)、答えの冒頭に追加しました。これは言語ではないことに注意してください仕様なので、私はそれを正確に学問的に正確にしようとはしていません-ちょうど参照カードのように。)

39
Antony Stubbs

あなたは答えにつまずいたようです。とにかく、私はそれを明確にしようとします。

プレフィックス表記、インフィックス表記、ポストフィックス表記を使用する場合は、ドットを省略できます。いわゆるoperator notation。演算子表記を使用しているときにのみ、メソッドに渡されるパラメーターが2つ未満の場合は、括弧を省略できます。

現在、演算子表記はmethod-callの表記です。つまり、呼び出されるオブジェクトがない場合は使用できません。

表記について簡単に説明します。

プレフィックス:

プレフィックス表記で使用できるのは、~!+、および-のみです。これは、!flagまたはval liability = -debtを記述するときに使用する表記法です。

中置:

これは、メソッドがオブジェクトとパラメーターの間に表示される表記法です。算術演算子はすべてここに適合します。

接尾辞(接尾辞も):

この表記法は、メソッドがオブジェクトに続き、パラメーターを受け取らない場合に使用されます。たとえば、list tailと書くことができます。これは後置記法です。

メソッドがカリー化されていない限り、中置表記法呼び出しを問題なく連鎖させることができます。たとえば、次のスタイルを使用します。

(list
 filter (...)
 map (...)
 mkString ", "
)

それは次と同じです:

list filter (...) map (...) mkString ", "

さて、フィルターとマップが単一のパラメーターを取る場合、なぜここで括弧を使用していますか?それは、匿名関数をそれらに渡しているからです。無名関数の終わりには境界が必要なので、匿名関数の定義と中置スタイルを混在させることはできません。また、匿名関数のパラメーター定義は、infixメソッドの最後のパラメーターとして解釈される場合があります。

複数のパラメーターで中置を使用できます。

string substring (start, end) map (_ toInt) mkString ("<", ", ", ">")

カリー化された関数は、中置記法では使いにくいです。折りたたみ関数はその明確な例です。

(0 /: list) ((cnt, string) => cnt + string.size)
(list foldLeft 0) ((cnt, string) => cnt + string.size)

中置呼び出しの外側で括弧を使用する必要があります。ここでの正確なルールは定かではありません。

それでは、後置について説明しましょう。 Postfixは、式の終わり以外の場所では使用できないため、使用が難しい場合があります。たとえば、次のことはできません。

 list tail map (...)

式の最後にテールが表示されないため。これもできません:

 list tail length

括弧を使用して式の終わりを示すことにより、中置表記法を使用できます。

 (list tail) map (...)
 (list tail) length

安全でない可能性がある であるため、後置記法は推奨されないことに注意してください。

これですべての疑問が解決されたことを願っています。そうでない場合は、コメントをドロップするだけで、改善のためにできることを確認できます。

83

さまざまな条件への洞察を与える引用のコレクション...

個人的には、仕様にはもっとあると思いました。私は正しい言葉を探しているだけではないはずです...

しかし、いくつかのソースがあり、それらを一緒に収集しましたが、実際に完全/包括的/理解可能/上記の問題を説明するものはありません...:

「メソッド本体に複数の式がある場合、中括弧{…}で囲む必要があります。メソッド本体に式が1つしかない場合は、中括弧を省略できます。」

第2章、「より少ない入力、より多くの入力」、ofProgramming Scala

「上のメソッドの本体は等号 '='の後に来る。なぜ等号なのか?なぜJavaのように中括弧{…}だけではないのか?セミコロン、関数の戻り値の型、メソッド引数リスト、さらに中括弧等号を使用すると、いくつかの構文解析のあいまいさを防ぐことができます。スカラ。」

1章「ゼロから60へ:Scalaの紹介」、からScalaプログラミング

「パラメータなしの関数は、括弧なしで宣言できます。この場合、括弧なしで呼び出す必要があります。これにより、Uniform Access Principleがサポートされます。これにより、呼び出し側は、シンボルが変数またはパラメーター。

値を返す場合(つまり、戻り値の型がUnit以外の場合)、関数本体の前に「=」が付きますが、戻り値の型と「=」は型がUnitの場合は省略できます(つまり、プロシージャのように見えます)関数とは対照的に)。

ボディを囲む中括弧は必要ありません(ボディが単一の式である場合)。より正確には、関数の本体は単なる式であり、複数の部分を含む式は中括弧で囲む必要があります(1つの部分を含む式はオプションで中括弧で囲むことができます)。

「引数が0個または1個の関数は、ドットと括弧なしで呼び出すことができます。ただし、式には括弧を付けることができるため、ドットを省略して括弧を使用できます。

また、括弧を使用できる場所ならどこでも中括弧を使用できるので、ドットを省略して、複数のステートメントを含むことができる中括弧に入れることができます。

引数なしの関数は、括弧なしで呼び出すことができます。たとえば、Stringのlength()関数は、「abc」.length()ではなく「abc」.lengthとして呼び出すことができます。関数がScala括弧なしで定義された関数である場合、関数は括弧なしで呼び出す必要があります。

慣例により、printlnなどの副作用のある引数のない関数は、括弧を付けて呼び出されます。副作用のないものは括弧なしで呼び出されます。」

ブログ投稿からScala Syntax Primer

「プロシージャ定義は、結果のタイプと等号が省略された関数定義です。その定義式はブロックでなければなりません。たとえば、def f(ps){stats}はdef f(ps)と同等です:Unit = {stats }。

例4.6.3以下に、writeという名前のプロシージャの宣言と定義を示します。

trait Writer {
    def write(str: String)
}
object Terminal extends Writer {
    def write(str: String) { System.out.println(str) }
}

上記のコードは、次のコードに暗黙的に補完されます。

trait Writer {
    def write(str: String): Unit
}
object Terminal extends Writer {
    def write(str: String): Unit = { System.out.println(str) }
}"

言語仕様から:

「単一のパラメータのみをとるメソッドでは、Scalaを使用すると、開発者は。をスペースに置き換えて括弧を省略することができ、挿入演算子の例に示す演算子構文を有効にできます。 Range =インスタンスの構築など、Scala APIの他の場所で:

val firstTen:Range = 0 to 9

ここでも、to(Int)はクラス内で宣言されたVanillaメソッドです(実際には、さらに暗黙的な型変換がいくつかありますが、ドリフトが発生します)。

FromJava Refugees Part 6:Get Over Java)

「今、「m 0」を試すと、Scalaは、有効な演算子(〜、!、-、+)ではないという理由で、単項演算子であることを破棄します。 「m」は有効なオブジェクトです。メソッドではなく関数であり、すべての関数はオブジェクトです。

「0」は有効なScala識別子ではないため、中置演算子でも後置演算子でもない。したがって、Scalaは、 "; -これは、2つの(ほぼ)有効な式を分離します:「m」と「0」。挿入すると、mが引数を必要とするか、または失敗すると「_」でそれを変換すると文句を言います。部分的に適用された機能。」

「演算子の構文スタイルは、左側に明示的なオブジェクトがある場合にのみ機能すると考えています。構文は、「オペランド演算子オペランド」スタイルの操作を自然な方法で表現できるようにすることを目的としています。」

Scalaではどの文字を省略できますか?

しかし、私を混乱させるのはこの引用です:

「メソッド呼び出しを受け取るにはオブジェクトが必要です。たとえば、printlnにはオブジェクトの受信者が必要なので、「println“ Hello World!”」を実行できません。 「Console println“ Hello World!”」を実行して、ニーズを満たします。」

私が見る限り、そこにはis呼び出しを受けるオブジェクトがあります...

12
Antony Stubbs

ありません。関数に副作用があるかどうかについてアドバイスを受けることがあります。これは偽です。修正は、Scalaが許可する合理的な範囲で副作用を使用しないことです。それができない限り、すべての賭けはオフです。 Allベット。括弧を使用することは、セット「all」の要素であり、不要です。すべてのベットがオフになると、値は提供されません。

このアドバイスは、本質的に失敗する エフェクトシステム の試みです(混同しないでください:他のエフェクトシステムよりも有用性が低い)。

副作用を起こさないようにしてください。その後、すべてのベットがオフであることを受け入れます。効果システムの事実上の構文表記法の後ろに隠れることは、害を引き起こす可能性があり、実際に行います。

2
user92983

実際、2回目の読み取りでは、これがキーになるかもしれません。

単一のパラメーターのみをとるメソッドでは、Scalaを使用すると、開発者は。をスペースに置き換え、括弧を省略できます

ブログの投稿で述べたように、 http://www.codecommit.com/blog/scala/scala-for-Java-refugees-part-6

おそらく、これは実際には非常に厳密な「構文糖」であり、onlyは、メソッドを効果的に呼び出している、 1つのパラメーターをとるオブジェクト。例えば.

1 + 2
1.+(2)

何もありません。

これは質問の私の例を説明するでしょう。

しかし、私が言ったように、誰かが言語仕様のどこにこれが指定されているかを正確に指摘できるなら、大歓迎です。

OK、ニースの仲間(#scalaのpaulp_)は、この情報が言語仕様のどこにあるかを指摘しています:

6.12.3:演算子の優先順位と結合性は、式の一部のグループ化を次のように決定します。

  • 式に複数の挿入演算子がある場合、優先順位の高い演算子は、優先順位の低い演算子よりも緊密にバインドします。
  • 連続する中置演算がある場合e0 op1 e1 op2。 。 .opn enと演算子op1、。 。 。 、同じ優先順位のopnの場合、これらの演算子はすべて同じ結合性を持つ必要があります。すべての演算子が左結合の場合、シーケンスは(。。。(e0 op1 e1)op2。。)opn enとして解釈されます。それ以外の場合、すべての演算子が右結合である場合、シーケンスはe0 op1(e1 op2(。。opn en)。。。)として解釈されます。
  • 後置演算子は常に中置演算子よりも優先順位が低くなります。例えば。 e1 op1 e2 op2は常に(e1 op1 e2)op2と同等です。

左結合演算子の右オペランドは、括弧で囲まれたいくつかの引数で構成されます。 e op(e1、。。。、en)。この式はe.op(e1、。。。、en)として解釈されます。

左結合二項演算e1 op e2は、e1.op(e2)として解釈されます。 opが右結合の場合、同じ操作は{val x = e1; e2.op(x)}、xは新しい名前です。

うーん-私にはそれが私が見ているものと一致しないか、私はそれを理解していません;)

2
Antony Stubbs

この経験則に従う方が簡単だと思います。式では、メソッドとパラメーターの間でスペースが交互に使用されます。この例では、_(service.findAllPresentations.get.first.votes.size) must be equalTo(2)_は_(service.findAllPresentations.get.first.votes.size).must(be)(equalTo(2))_として解析されます。 2を囲む括弧は、スペースよりも高い連想性を持っていることに注意してください。ドットは結合性が高いため、_(service.findAllPresentations.get.first.votes.size) must be.equalTo(2)_は_(service.findAllPresentations.get.first.votes.size).must(be.equalTo(2))_として解析されます。

_service findAllPresentations get first votes size must be equalTo 2_はservice.findAllPresentations(get).first(votes).size(must).be(equalTo).2として解析します。

2
Mario Camou