1. ホーム
  2. scala

[解決済み] Scala の currying と部分的に適用される関数

2023-07-13 03:51:34

質問

このサイトには、以下のような質問があるようです。 curryingと部分適用関数がありますが、どう違うのかについて質問しています。簡単な例として、偶数を求めるための curried 関数を紹介します。

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> 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)

そこで 複数パラメータリスト 記法は、(不必要なオーバーヘッドを避けるためと思われますが)実際にはすぐに curried 関数を作成するのではなく、curried にしたいことを明示的に指定するのを待ちます(この記法にはいくつかの その他の利点 もあります)。

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

これは前に得たものと全く同じものであり、表記を除けば違いはない。もう一つの例です。

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

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

これは、quot;normal" 関数を部分的に適用すると、すべてのパラメータを受け取る関数になり、一方、複数のパラメータリストを持つ関数を部分的に適用すると、関数のチェーンが作成されることを示しています。 パラメータリストごとに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 は "normal" 関数を部分的に適用する際に型を推論することができないようです。

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>

この回答 は、これが非常に有用であることを示しています。