8

私はScalaを初めて使用し、関数を定義する多くの方法を見てきましたが、違いと、いつどの形式を使用するかについての明確な説明を見つけることができませんでした。

次の関数定義の主な違いは何ですか?

  1. '='を使用

    def func1(node: scala.xml.Node) = {
        print(node.label + " = " + node.text + ",")
    }
    
  2. '='なし

    def func2 (node: scala.xml.Node) {
        print(node.label + " = " + node.text + ",")
    }
    
  3. '=>'を使用

    def func3 = (node: scala.xml.Node) => {
        print(node.label + " = " + node.text + ",")
    }
    
  4. varとして

    var func4 = (node: scala.xml.Node) => {
        print(node.label + " = " + node.text + ",")
    }
    
  5. ブロックなし

    def func5 (node: scala.xml.Node) = print(node.label + " = " + node.text + ",")  
    

それらはすべて、のコールバックとして使用されたときに同じ結果をコンパイルしてレンダリングするようです

    xmlNodes.iterator.foreach(...)
  • それぞれが生成するバイトコードに違いはありますか?
  • どのフォームをいつ使用するかについてのガイドラインはありますか?
4

4 に答える 4

19

これらの質問のそれぞれは、このサイトの他の場所で回答されていますが、私はそれらをすべて一緒に扱うものはないと思います。それで:

中括弧と等しい

等号で定義されたメソッドは、値を返します(最後に評価されたものは何でも)。中括弧のみで定義されたメソッドはを返しUnitます。等しいを使用するが、最後のものがに評価されるUnit場合、違いはありません。等号の後の単一のステートメントの場合、中括弧は必要ありません。これはバイトコードに違いはありません。したがって、1。、2。、および5.はすべて本質的に同一です。

def f1(s: String) = { println(s) }     // println returns `Unit`
def f2(s: String) { println(s) }       // `Unit` return again
def f5(s: String) = println(s)         // Don't need braces; there's only one statement

関数とメソッド

多くの場合、と書かれる関数は、クラスA => Bの1つのサブクラスです。このクラスにはメソッドがあり、メソッド名なしでparensを使用すると、Scalaが魔法のように呼び出すため、メソッド呼び出しのように見えます。ただし、そのオブジェクトに対する呼び出しである点が異なります。だからあなたが書くならFunctionFunction1[A,B]applyFunction

def f3 = (s: String) => println(s)

次に、あなたが言っているのは、「のようなメソッドを持つf3インスタンスを作成する必要がある」ということです。つまり、と言うと、これは最初に関数オブジェクトを作成するための呼び出しであり、次にメソッドを呼び出します。Function1[String,Unit]applydef apply(s: String) = println(s)f3("Hi")f3apply

関数オブジェクトを使用するたびに作成するのはかなり無駄なので、関数オブジェクトを変数に格納する方が理にかなっています。

val f4 = (s: String) => println(s)

これは、(メソッド)が返すのと同じ関数オブジェクトのインスタンスを1つ保持するdefため、毎回再作成する必要はありません。

いつ何を使うか

: Unit = ...との慣習は人によって異なります{ }。個人的にはUnit、等号なしで返されるすべてのメソッドを記述します。これは、何らかの副作用(変数の変更、IOの実行など)がない限り、メソッドがほぼ確実に役に立たないことを示しています。また、私は通常、複数のステートメントがあるか、単一のステートメントが非常に複雑であるため、必要な場合にのみ中括弧を使用します。視覚的な補助で、それがどこで終了するかを教えてください。

メソッドは、必要なときにいつでも使用する必要があります。関数オブジェクトは、他のメソッドに渡して使用するときはいつでも作成する必要があります(または、関数を適用できるようにするときはいつでもパラメーターとして指定する必要があります)。たとえば、値をスケーリングできるようにしたいとします。

class Scalable(d: Double) {
  def scale(/* What goes here? */) = ...
}

定数乗数を指定できます。または、追加するものと乗算するものを提供することもできます。Doubleしかし、最も柔軟な方法として、からまでの任意の関数を要求するだけですDouble

def scale(f: Double => Double) = f(d)

さて、あなたはデフォルトのスケールのアイデアを持っているかもしれません。それはおそらくスケーリングではありません。したがって、を取りDouble、まったく同じを返す関数が必要になる場合がありますDouble

val unscaled = (d: Double) => d

val関数を何度も作成し続けたくないので、関数をに格納します。これで、この関数をデフォルトの引数として使用できます。

class Scalable(d: Double) {
  val unscaled = (d: Double) => d
  def scale(f: Double => Double = unscaled) = f(d)
}

x.scaleこれで、andx.scale(_*2)とandの両方を呼び出すことができ、x.scale(math.sqrt)すべて機能します。

于 2012-06-21T15:13:18.850 に答える
5

はい、バイトコードには違いがあります。そして、はい、ガイドラインがあります。

  1. With =:これは、パラメーターを受け取り、右側のブロックの最後の式を返すメソッドを宣言します。このタイプは、Unitここにあります。

  2. なし=:これは、戻り値を持たないメソッドを宣言します。つまりUnit、右側のブロックの最後の式のタイプに関係なく、戻りタイプは常にです。

  3. With :これは、型の関数オブジェクト=>を返すメソッドを宣言します。このメソッドを呼び出すたびに、ヒープ上に新しい関数オブジェクトを作成します。を書く場合は、最初に関数オブジェクトを返すを呼び出し、次にその関数オブジェクトでを呼び出します。これは、ケース1と2のように、単純なメソッドを直接呼び出すよりも遅くなります。scala.xml.Node => Unitfunc3func3(node)func3apply(node)

  4. としてvar:これは変数を宣言し、3。のように関数オブジェクトを作成しますが、関数オブジェクトは1回だけ作成されます。これを使用して関数オブジェクトを呼び出すと、ほとんどの場合、単なるメソッド呼び出し(JITによってインライン化されない場合があります)よりも遅くなりますが、少なくともオブジェクトを再作成することはありません。誰かが変数を再割り当てする危険を回避したい場合は、代わりにまたはfunc4を使用してください。vallazy val

  5. これは、ブロックに1つの式のみが含まれている場合の1の構文糖衣です。

1、2、5の形式を高階メソッドで使用する場合でも、Scalaは、または暗黙的にforeach呼び出す関数オブジェクトを作成し、それをに渡します(メソッドハンドルまたはsmthのようなものは使用しません)。それは、少なくとも現在のバージョンではありません)。このような場合、生成されるコードはおおよそ次のようになります。func1func2func5foreach

xmlNodes.iterator.foreach((node: scala.xml.Node) => funcX(node))

したがって、ガイドラインは次のとおりです。毎回同じ関数オブジェクトを使用する場合を除いて、1、2、または5のように通常のメソッドを作成するだけです。とにかく、これが必要な場合は関数オブジェクトに持ち上げられます。このようなメソッドの呼び出しが頻繁に発生するため、これにより多くのオブジェクトが生成されることに気付いた場合は、代わりにフォーム4を使用してマイクロ最適化し、の関数オブジェクトforeachが1回だけ作成されるようにすることをお勧めします。

1.、2。、5。のいずれかを決定する場合、1つのガイドラインは次のとおりです。単一のステートメントがある場合は、フォーム5を使用します。

それ以外の場合、リターンタイプが、の場合、これがパブリックAPIの場合はフォームUnitを使用して、コードをすばやく確認するクライアントがリターンタイプを明確に確認できるようにします。def foo(): Unit = {短いコードを使用するために、プライベートなdef foo() {return型のメソッドのフォームを使用してください。Unitしかし、これはスタイルに関する1つの特定のガイドラインにすぎません。

詳細については、http://docs.scala-lang.org/style/declarations.html#methodsを参照してください。

于 2012-06-21T15:03:46.523 に答える
2

1、2、5は関数はなく、メソッドであり、関数とは根本的に異なります。メソッドはオブジェクトに属し、それ自体はオブジェクトではありませんが、関数オブジェクトです。

1、2、および5もまったく同じです。ステートメントが1つしかない場合、複数のステートメントをグループ化するために中括弧は必要ありません。ergo5は1と同じです。=記号を省略すると、の戻りタイプですがUnitUnit1と5の推定戻りタイプでもあるため、2は1と5と同じです。

3は、呼び出されたときに関数を返すメソッドです。4は関数を指す変数です。

于 2012-06-21T15:06:14.237 に答える
1

1-2。等号を捨てると、関数はプロシージャになります(Unitを返すか、何も返しません)。
3. 3番目のケースscala.xml.Node => Unitでは、関数を返す関数を定義しました。4.同じですが、変数
にいくつかの関数を割り当てました。違いは、Scala 5で関数を定義するこれらの3つの方法のscala.xml.Node => Unit違いで説明され ています。違いはありません。1と比較してください。しかし、そのような複数行のステートメントを書くことはできません。

于 2012-06-21T15:03:57.627 に答える