web-dev-qa-db-ja.com

Scalaカリー化と部分的に適用された関数

ここにwhatカリー化と部分的に適用された関数についていくつかの質問があることを理解していますが、私はどのように尋ねています彼らは違う。簡単な例として、偶数を見つけるためのカリー化された関数を次に示します。

_def filter(xs: List[Int], p: Int => Boolean): List[Int] =
   if (xs.isEmpty) xs
   else if (p(xs.head)) xs.head :: filter(xs.tail, p)
   else filter(xs.tail, p)

def modN(n: Int)(x: Int) = ((x % n) == 0)
_

したがって、これを使用するには次のように記述できます。

_val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))
_

List(2,4,6,8)を返します。しかし、この方法で同じことができることがわかりました。

_def modN(n: Int, x: Int) = ((x % n) == 0)

val p = modN(2, _: Int)
println(filter(nums, p))
_

List(2,4,6,8)も返します。

それで、私の質問は、2つの主な違いは何ですか、そしていつあなたは一方を他方の上に使いますか?これは、一方が他方に対して使用される理由を示すには単純すぎますか?

81
Eric

意味の違いは Plasty Groveからリンクされた答え でかなりよく説明されています。

ただし、機能面では大きな違いはありません。それを確認するためにいくつかの例を見てみましょう。まず、通常の機能:

_scala> def modN(n: Int, x: Int) = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>
_

そのため、最初の整数が既に与えられているため、Intを取る部分的に適用された_<function1>_を取得します。ここまでは順調ですね。カレーに今:

_scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0)
_

この表記法を使用すると、次のものが機能すると単純に期待できます。

_scala> modNCurried(5)
<console>:9: error: missing arguments for method modN;
follow this method with `_' if you want to treat it as a partially applied function
          modNCurried(5)
_

したがって、複数のパラメータリスト表記は、カリー化された関数をすぐに作成するようには見えませんが(不要なオーバーヘッドを避けるために)、明示的に述べるのを待っていますあなたはそれをカリーにしたい(表記にはいくつかの 他の利点 もあります):

_scala> modNCurried(5) _
res24: Int => Boolean = <function1>
_

これは以前とまったく同じものなので、表記を除いてここでは違いはありません。もう一つの例:

_scala> modN _
res35: (Int, Int) => Boolean = <function2>

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>
_

これは、「通常の」関数を部分的に適用すると、すべてのパラメーターを受け取る関数になることを示していますが、複数のパラメーターリストを含む関数を部分的に適用すると、パラメーターリストごとに1つの関数チェーンが作成されますこれは、すべて新しい関数を返します:

_scala> def foo(a:Int, b:Int)(x:Int)(y:Int) = a * b + x - y
scala> foo _
res42: (Int, Int) => Int => (Int => Int) = <function2>

scala> res42(5)
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2.
Unspecified value parameter v2.
_

ご覧のとおり、fooの最初のパラメーターリストには2つのパラメーターがあるため、カリーチェーンの最初の関数には2つのパラメーターがあります。


要約すると、部分的に適用された関数は、機能の点で実際にはカリー化された関数とは異なりません。これは、任意の関数をカリー化された関数に変換できることを考えると、簡単に検証できます。

_scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>
_

ポスト台本

注:println(filter(nums, modN(2))の後にアンダースコアなしでmodN(2)が機能する理由は、= Scalaコンパイラは、単にアンダースコアをプログラマの利便性として想定しています。


追加:@asflierlが正しく指摘しているように、Scalaは次の場合に型を推測できないようです。 「通常の」関数を部分的に適用する:

_scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))
_

その情報は、複数のパラメーターリスト表記を使用して記述された関数で利用できます。

_scala> modNCurried(5) _
res3: Int => Boolean = <function1>
_

これが答えます は、これが非常に役立つ方法を示しています。

87
fresskoma

カリー化は、タプルを使用することです:Tuple引数を取る関数を、n個の別個の引数を取る関数に変換する、またはその逆。これを覚えておくことは、カレーをきちんとサポートしていない言語であっても、カレーと部分的なアプリケーションを区別する鍵となります。

curry :: ((a, b) -> c) -> a -> b -> c 
   -- curry converts a function that takes all args in a Tuple
   -- into one that takes separate arguments

uncurry :: (a -> b -> c) -> (a, b) -> c
   -- uncurry converts a function of separate args into a function on pairs.

部分適用とは、一部の引数に関数を適用し、残りの引数に新しい関数を生成するの機能です。

カリー化がタプルを使用した変換であると考えている場合は覚えやすいです。

デフォルトでカリー化された言語(Haskellなど)では、違いは明らかです。実際には、タプルで引数を渡すために何かをする必要があります。しかし、Scalaを含む他のほとんどの言語はデフォルトではカリー化されていません-すべての引数はタプルとして渡されるため、カリー/カレンシーはあまり有用ではなく、あまり明白ではありません。そして、人々は、部分的なアプリケーションとカリー化は同じものだとさえ考えてしまいます-カリー化された関数を簡単に表現できないからです!

19
Don Stewart

多変数関数:

def modN(n: Int, x: Int) = ((x % n) == 0)

カリー化(またはカリー化された関数):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

したがって、カリー化に匹敵する部分的に適用された機能ではありません。それは多変数関数です。部分的に適用された関数に匹敵するのは、カリー化された関数の呼び出し結果です。これは、部分的に適用された関数と同じパラメーターリストを持つ関数です。

2
lcn

最後のポイントを明確にするためだけに

追加:@asflierlが正しく指摘しているように、Scalaは「通常の」関数を部分的に適用するときに型を推測できないようです:

Scalaは、すべてのパラメーターがワイルドカードである場合に型を推測できますが、一部のパラメーターが指定されていて一部が指定されていない場合は推測できません。

scala> modN(_,_)
res38: (Int, Int) => Boolean = <function2>

scala> modN(1,_)
<console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1))
       modN(1,_)
              ^
0
Sud