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


83

私が管理しているScalaスタイルガイドで複数のパラメータリストについて話し合っています。私はカリー化には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番目の形式は「本による」カリー化ではありませんが、最初の形式と非常によく似ています(ただし、必要がないため、間違いなく使いやすいです)。インクルード_

これらのフォームを使用するものから、一方のフォームをもう一方のフォームよりもいつ使用するかについてのコンセンサスは何ですか?

回答:


137

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

型推論の場合

複数のパラメーターセクションを持つメソッドを使用して、最初のセクションのパラメーターを使用して、後続のセクションの引数に期待される型を提供する型引数を推論することにより、ローカル型の推論を支援できます。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)

流暢な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リファレンスのScala2.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!
}

3
素晴らしい答え。1つだけではなくもっと多くの賛成票があればいいのにと思います。ダイジェストしてスタイルガイドに適用します。私が成功した場合、これが選択された答えです…
davetron5000 2011

1
:あなたは、あなたにループの例を修正したい場合がありますdef loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)
オマールバンKloeten

これはコンパイルされません:val f: (a: Int) => (b: Int) => (c: Int) = a + b + c
nilskp 2012年

「N個の引数リストをM個のパラメーターセクションを持つメソッドに適用すると、N <Mは、明示的に_を使用して、または暗黙的に、予想されるタイプのFunctionN [..]を使用して関数に変換できます。」<br/>それはFunctionX [..]であるべきではありません、ここでX = MN?
stackoverflower 2013年

"これはコンパイルされません:val f:(a:Int)=>(b:Int)=>(c:Int)= a + b + c"私は思いません "f:(a:Int)= >(b:Int)=>(c:Int) "は正しい構文です。おそらくレトロニムは「f:Int => Int => Int => Int」を意味していました。=>は右結合であるため、これは実際には「f:Int =>(Int =>(Int => Int))」です。したがって、f(1)(2)の型はInt => Int(つまり、fの型の最も内側のビット)です
stackoverflower 2013年

16

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

メソッドと関数の違いを考えると(たとえば、JVMの観点から)、Scalaはそれらの間の境界線を曖昧にし、ほとんどの場合「正しいこと」を実行するのにかなり良い仕事をします、違いがあり、場合によって必要なだけです。それについて知るために。


これは理にかなっているので、フォームdef add(a:Int)(b:Int)を何と呼びますか?そのdefとdefadd(a:Int、b:Int)の違いを説明する用語/フレーズは何ですか?
davetron5000 2011

@ davetron5000最初のメソッドは複数のパラメーターリストを持つメソッドで、2番目のメソッドは1つのパラメーターリストを持つメソッドです。
ジェスパー

5

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.