かっこ、ドット、中かっこ、=(関数)などを省略できる正確な規則は何ですか?


106

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

例えば、

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service 私の目的は
  • def findAllPresentations: Option[List[Presentation]]
  • votes 戻り値 List[Vote]
  • 仕様の機能である必要があります

なぜ行けないのですか:

(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)にする必要がある理由:

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

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

Scalaの本とWebサイトを調べましたが、包括的な説明は本当に見つかりません。

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

回答:


87

あなたは答えに出くわしたようです。とにかく、はっきりさせておきます。

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

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

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

接頭辞:

のみ~!+および-接頭語の表記に使用することができます。これは、!flagまたはを書くときに使用する表記ですval liability = -debt

インフィックス:

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

Postfix(サフィックスも):

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

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

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

それは同じことです:

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

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

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

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

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

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

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

それでは、postfixについて話しましょう。Postfix は、式の終わりを除いてどこでも使用できないため、使いにくい場合があります。たとえば、次のことはできません。

 list tail map (...)

尾が表情の最後に現れないからです。あなたもこれを行うことはできません:

 list tail length

括弧を使用して式の終わりを示すことにより、インフィックス表記を使用できます。

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

安全ではない可能性があるため、後置表記はお勧めしませ

これですべての疑問が解消されたことを願っています。そうでない場合は、コメントをドロップしてください。改善するために私にできることがわかります。


ああ、あなたは私の声明で言っている:(((((realService findAllPresentations)get)first)votes)size)is equalTo 2-get、first、votesとsizeはすべて後置演算子であり、パラメータを取らないため?だから私は何である必要がありますか、equalToは...
アントニー・

私はそのようなことは何も言っていませんが、それは事実だと確信しています。:-) "be"はおそらく構文をきれいにするためのヘルパーオブジェクトです。または、より具体的には、「必須」のインフィックス表記の使用を有効にします。
ダニエルC.ソブラル

さて、取得はOption.get、最初はlist.first、投票はケースクラスプロパティ、サイズはlist.sizeです。あなた今何考えてるの?
アントニー

ああそうです-これはすべて、「(realService findPresentation 1).get.id is equalTo 1」が機能するという事実によって補強されます-Service#findPresentations(id:Int)は、中置演算子のようです。かっこいい-もう理解できたと思う。:)
アントニー・スタブス

42

クラス定義:

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

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

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

class EmptyClass

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

コンパイラが推測できる場合は、ジェネリックパラメータを省略できます。ただし、タイプが一致しない場合、タイプパラメータは常に一致するように推測されます。したがって、タイプを指定しないと、期待どおりの結果が得られない可能性があります。つまり、

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

これにより、タイプエラーが発生します(Intが見つかりました、予期された文字列)

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

これは正常に動作しますが、

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

型パラメーター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) { .. }

ステートメント:

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

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

  • () postfix演算子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

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

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、戻り値の型の削除を多用すると、コードを再利用するのに害を及ぼす可能性があります。コードに明示的な情報がないために、可読性が低下する例として仕様をご覧ください。変数のタイプが実際に何であるかを理解するための間接参照のレベルの数は、ナットになる可能性があります。うまくいけば、より良いツールがこの問題を回避し、コードを簡潔に保つことができます。

(わかりました。より完全で簡潔な回答をまとめるために(何かを見落としたり、何か間違い/不正確だったりしたらコメントしてください)、回答の最初に追加しました。これは言語ではないことに注意してください仕様なので、私はそれを学問的に正確にしようとはしていません-ちょうど参照カードのように。)


10
私は泣いています。これは何ですか。
Profpatsch 2015年

12

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

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

しかし、いくつかの出典があり、一緒に収集しましたが、上記の問題を私に説明する完全で包括的な/理解できる/理解できるものはありません...:

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

第2章の「タイプ以下、ドゥもっと」、Scalaのプログラミング

「上部メソッドの本文は等号「=」の後に来ます。なぜ等号ですか。Javaのように中括弧{…}だけではないのですか?セミコロン、関数の戻り値の型、メソッド引数リスト、さらには中括弧さえ等号を使用すると、いくつかの解析のあいまいさを防ぐことができます。等号を使用すると、関数でもScalaの値であることがわかります。これは、第8章「関数型プログラミング」で詳しく説明されているScalaの関数型プログラミングのサポートと一致しています。 Scala。」

以下からの第1章、「ゼロシックスティーへ:Scalaの概要」の、Scalaのプログラミングを

「パラメーターのない関数は括弧なしで宣言できます。その場合、括弧なしで呼び出す必要があります。これにより、呼び出しがシンボルが変数であるか変数のない関数であるかわからないように、統一アクセス原則がサポートされます。パラメーター。

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

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

「ゼロまたは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では開発者が。をスペースに置き換えて括弧を省略できるため、挿入演算子の例に示されている演算子構文が有効になります。この構文は、Scala APIの他の場所で使用されています。 Rangeインスタンスを作成する場合:

val firstTen:Range = 0 to 9

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

以下からのJava難民パート6のためのScala:オーバーのJavaの取得

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

"0"は有効なScala識別子ではないため、中置演算子でも後置演算子でもありません。したがって、Scalaは「;」を期待したと不平を言います。-2つの(ほぼ)有効な式「m」と「0」を分離します。挿入した場合、mは引数を必要とするか、失敗した場合は "_"を使用して部分的に適用される関数に変換する必要があると警告します。

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

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

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

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

私が見る限りで、電話を受けるオブジェクトがあるからです...


1
さて、Specのソースを読んで手掛かりと驚きを手に入れようとしました。これは、マジックコードの問題の良い例です。ミックスイン、型推論、暗黙の変換、暗黙のパラメーターが多すぎます。外からはわかりにくい!そのような大きなライブラリの場合、優れたツールはいくつかの不思議を引き起こす可能性があります...ある日...
アントニースタッブズ

3

この経験則に従う方が簡単だと思います。式では、スペースはメソッドとパラメーターを交互に使用します。あなたの例で(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

実際、2番目の読書では、おそらくこれがキーです。

単一のパラメーターのみを取るメソッドの場合、Scalaでは開発者がを置き換えることができます。スペースを入れて括弧を省略します

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

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

1 + 2
1.+(2)

何もありません。

これは私の質問の例を説明します。

しかし、私が言ったように、誰かが言語仕様のどこに正確にあるかを指摘することができれば、それは非常にありがたいです。

さて、いくつかの素敵な仲間(#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は新しい名前です。

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


うーん、さらに混乱を増すために、これも有効です:((((((realService findAllPresentations)get)first)votes)はequalTo 2でなければなりませんが、これらの括弧のペアのいずれかを削除した場合はそうではありません...
アントニースタブ

2

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

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

副作用を起こさないようにしてください。その後、すべての賭けがオフであることを受け入れます。エフェクトシステムの事実上の構文表記法の背後に隠れることは、害を及ぼすだけであり、実際に害を及ぼします。


まあ、でもそれがハイブリッドOO /関数型言語で作業しているときの問題ですよね?実用的な例として、副作用機能が必要になる場合があります...「エフェクトシステム」に関する情報を教えていただけますか?私は引用符をもっと指摘する必要があると思います「パラメータなしの関数は括弧なしで宣言できます。その場合、括弧なしで呼び出す必要があります。これは、呼び出し元がシンボルは変数またはパラメータのない関数です。
アントニースタブ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.