Scalaカリー化と部分的に適用された機能


82

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

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つの主な違いは何ですか、そしていつどちらを使用するのですか?これは、例が単純すぎて、一方が他方よりも使用される理由を示すことができないのでしょうか。


部分的に適用すると、メモリ内のカレーバージョンと非カレーバージョンを表すコストが異なる可能性があるため、実行時のパフォーマンスにも影響する可能性があります。(つまり、オプティマイザーが両方の場合に最適な表現を選択するのに十分賢くない場合です。)しかし、私はScalaに精通していないため、正確な違いは何ですか。


私はこの説明が非常に役立つと思いました。彼は部分関数、部分適用関数、カリー化についてすべて1つの投稿で説明しています:stackoverflow.com/a/8650639/1287554
Plasty Grove 2013

すばらしいリンク、@ PlastyGrove。ありがとうございました!
エリック

そして、あなたのリンクをありがとう@Utaal。マーティン・オーダスキー自身からのどんな答えも非常に高く評価されています。これらの概念は今クリックし始めていると思います。
エリック

回答:


88

意味の違いは、PlastyGroveによってリンクされた回答でかなりよく説明されています。

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

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

したがって、最初の整数をすでに指定しているため、<function1>をとる部分的に適用されますInt。ここまでは順調ですね。カリー化へ:

scala> def modNCurried(n: Int)(x: Int): Boolean = ((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): 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>

この回答は、これがどのように非常に役立つかを示しています。


重要な違いの1つは、型推論の動作が異なることです。gist.github.com

ありがとう、私はあなたのコメントに関するメモを追加しました:)
fresskoma 2013年

19

カリー化はタプルと関係があります。タプル引数をとる関数を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を含む他のほとんどの言語は、デフォルトではカレーされていません。すべての引数はタプルとして渡されるため、カレー/カレーははるかに有用性が低く、わかりにくいものです。そして、部分適用とカリー化は同じものだと思ってしまう人もいます。カリー化された機能を簡単に表現できないからです。


私は完全に同意します。Scalaの用語では、単語の本来の意味での「カリー化」は、1つのパラメーターリストを持つ関数を複数のパラメーターリストを持つ関数に「変換するプロセス」です。Scalaでは、この変換は「.curried」を使用して実行できます。残念ながら、Scalaは元々、「。curried」ではなく「.curry」と呼ばれることを望んでいたため、単語の意味を少し過負荷にしたようです。
ファティCoşkun

2

多変数関数:

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

カリー化(またはカリー化機能):

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

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


0

最後の点を明確にするために

追加:@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

私がこれまでに見つけた最良の説明:https//dzone.com/articles/difference-between-currying-amp-partially-applied

カリー化:複数の引数を持つ関数を単一の引数の関数のチェーンに分解します。Scalaでは、関数を引数として別の関数に渡すことができることに注意してください。

関数の部分適用:宣言にあるよりも少ない引数を関数に渡します。関数に提供する引数の数が少ない場合、Scalaは例外をスローしません。単にそれらを適用し、渡す必要のある残りの引数を含む新しい関数を返します。

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