web-dev-qa-db-ja.com

Scalaでカレーする2つの方法。それぞれのユースケースは何ですか?

Scalaスタイルガイドで維持しているMultiple Parameter Lists)について議論しています。 currying には2つの方法があることに気づき、ユースケースは何だろうと思います。

def add(a:Int)(b:Int) = {a + b}
// Works
add(5)(6)
// Doesn't compile
val f = add(5)
// Works
val f = add(5)_
f(10) // yields 15

def add2(a:Int) = { b:Int => a + b }
// Works
add2(5)(6)
// Also works
val f = add2(5)
f(10) // Yields 15
// Doesn't compile
val f = add2(5)_

スタイルガイドは、これらが明らかに異なる場合でも、これらが同じであることを誤って示唆しています。ガイドは作成されたカリー化された関数についてポイントを作成しようとしています。2番目のフォームは「予約による」カリー化ではありませんが、それでも最初のフォームと非常に似ています(ただし、おそらく必要ないので使いやすいでしょう) _

これらのフォームを使用するものから、あるフォームを他のフォームよりも使用する場合のコンセンサスは何ですか?

83
davetron5000

複数のパラメーターリストメソッド

型推論の場合

複数のパラメーターセクションを持つメソッドは、最初のセクションのパラメーターを使用して、後続のセクションの引数に期待される型を提供する型引数を推論することにより、ローカル型推論を支援するために使用できます。標準ライブラリのfoldLeftは、この標準的な例です。

_def foldLeft[B](z: B)(op: (B, A) => B): B

List("").foldLeft(0)(_ + _.length)
_

これが次のように書かれた場合:

_def foldLeft[B](z: B, op: (B, A) => B): B
_

より明示的なタイプを提供する必要があります。

_List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)
_

Fluent APIの場合

複数のパラメータセクションメソッドのもう1つの用途は、言語構造のように見えるAPIを作成することです。呼び出し元は、括弧の代わりに中括弧を使用できます。

_def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)

loop(2) {
   println("hello!")
}
_

N引数リストをMパラメータセクションを持つメソッドに適用すると、N <Mの場合、___を使用して明示的に、または_FunctionN[..]_の予期されるタイプを使用して暗黙的に関数に変換できます。これは安全機能です。背景については、ScalaリファレンスのScala 2.0、の変更点を参照してください。

カレー関数

カリー化された関数(または単に、関数を返す関数)は、N個の引数リストにより簡単に適用できます。

_val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)
_

このマイナーな便利さは時々価値があります。ただし、関数はパラメトリック型にすることはできないため、場合によってはメソッドが必要になります。

2番目の例はハイブリッドです。関数を返す1パラメータセクションメソッドです。

多段階計算

カリー化された関数が他に役立つのはどこですか?ここにいつも現れるパターンがあります:

_def v(t: Double, k: Double): Double = {
   // expensive computation based only on t
   val ft = f(t)

   g(ft, k)
}

v(1, 1); v(1, 2);
_

結果をどのように共有できますかf(t)?一般的な解決策は、vのベクトル化バージョンを提供することです。

_def v(t: Double, ks: Seq[Double]: Seq[Double] = {
   val ft = f(t)
   ks map {k => g(ft, k)}
}
_

醜い! g(f(t), k)を計算し、ksのシーケンスにマッピングして、無関係な懸念を巻き込みました。

_val v = { (t: Double) =>
   val ft = f(t)
   (k: Double) => g(ft, k)       
}
val t = 1
val ks = Seq(1, 2)
val vs = ks map (v(t))
_

関数を返すメソッドを使用することもできます。この場合、それはもう少し読みやすいです:

_def v(t:Double): Double => Double = {
   val ft = f(t)
   (k: Double) => g(ft, k)       
}
_

しかし、複数のパラメータセクションを持つメソッドで同じことを実行しようとすると、行き詰まります。

_def v(t: Double)(k: Double): Double = {
                ^
                `-- Can't insert computation here!
}
_
132
retronym

カリー化できるのはメソッドではなく関数のみです。 addはメソッドであるため、関数に強制的に変換するには_が必要です。 add2は関数を返すため、_は不要であるだけでなく、ここでは意味がありません。

メソッドと関数の違い(JVMの観点など)を考慮すると、Scalaは、ほとんどの場合、それらの間の線をぼかして「正しいこと」を行うのにかなり良い仕事をしますが、 is違い、時にはそれについて知る必要があるだけです。

16
Landei

それをdef add(a: Int)(b: Int): Intで追加すると、違いを把握するのに役立つと思いますtwoパラメータを使用してメソッドを定義するだけで、これらの2つのパラメータのみが2つのパラメータリストにグループ化されます(他のコメントでその結果)。実際、そのメソッドはJava(Scalaではない!)に関する限り)はint add(int a, int a)です。add(5)_と書くと、それは単なる関数リテラルです。 { b: Int => add(1)(b) }の短縮形。一方、add2(a: Int) = { b: Int => a + b }では、パラメータが1つだけのメソッドを定義し、Javaの場合、scala.Function add2(int a)。Scalaでadd2(1)と書くと、それは(関数リテラルではなく)単なるメソッド呼び出しです。

また、すべてのパラメーターをすぐに指定すると、addのオーバーヘッドが(潜在的に)add2よりも小さくなることに注意してください。 add(5)(6)がJVMレベルでadd(5, 6)に変換されるように、Functionオブジェクトは作成されません。一方、add2(5)(6)は最初に5を含むFunctionオブジェクトを作成し、次にapply(6)を呼び出します。

5
ddekany