Edited at

Scalaでカリー化はあまり役に立たない

タイトルは釣りではなくて、普通に真面目な話です。

結論だけ書くと、カリー化という手法を使う意義は、概念的には、N個の引数をとる関数という概念を、1個の引数をとる関数だけを使って表現できること、実用的にはそういう風に表現することによって、関数の前からの部分適用が簡単にできることですが、Scalaの場合は前者については、普通にN引数メソッドやN引数関数があるので意味がないし、後者については、そのための専用構文があるので有用性が微妙ということになります。

で、それだと色々すっ飛ばし過ぎなので、カリー化ってそもそも何をするものなのか、についてざっくりと説明します。カリー化というのは、Scalaでいうと、たとえば、2引数の足し算をする関数を

val add: (Int, Int) => Int = (x, y) => x + y

という表現する代わりに、

val curriedAdd: Int => Int => Int = x => y => x + y

という形で表現することです。より一般的にいうと、

def curry[A, B, C](f: (A, B) => C): A => B => C = x => y => f(x, y)

こういうメソッドでやっていることがカリー化ということになります(2引数のとき限定でいうなら)。このcurryメソッドに対して、最初のadd関数を以下のようにして渡すと、

val curriedAdd = curry(add)

以下のようにして書けるわけです。

val pAdd = curriedAdd(2)

val result = pAdd(3)
println(result) // 5

さて、こういうことができると何が嬉しいかというと、主に、次のように、一部の引数だけを適用(部分適用)することができる、ということが挙げられます。

val xs = List(1, 2, 3, 4).map(curriedAdd(2))

println(xs) // List(3, 4, 5, 6)

しかし、実のところ、Scalaでそれをやりたければ、次のようにプレースホルダ構文を使えばいいのです。

List(1, 2, 3, 4).map(add(2, _))

また、カリー化された関数は部分適用できるといっても、前からしか部分適用できないので、プレースホルダ構文の方が汎用性があります(ただし、プレースホルダ構文は純粋に構文的操作であって、型を考慮してくれないので、その点に注意する必要があります)。

また、Scalaだと、JVMとの相互運用性を考慮して、という都合もあるかと思いますが、以下のように、複数の引数をとるメソッド複数の引数をとる関数が普通に存在してしまっているので、1引数関数だけで全部表現できる、という概念上の単純化にも特に役に立っていません。

def foo1(a: Int, b: Int, c: Int, d: Int): Int = a + b + c + d

val foo2: (Int, Int, Int, Int) => Int = (a, b, c, d) => a + b + c + d

というわけで、ことScalaにおいては、あまりカリー化という操作をすることにあまり意味がない、というのが私の意見です(一般的な概念としてのカリー化を知っておくことは意義があると思います)。

余談ですが、Scalaでは標準ライブラリにcurriedメソッドがあり、以下のようにして使うことができるので、自分でカリー化するための操作を定義する必要はありません。

val sub: (Int, Int) => Int = (x, y) => x - y

val curriedSub = sub.curried
val pApp = curriedSub(1)
println(pApp(2)) // -1

ただ、この curried メソッド、私は実用で使ったことが一度もないですし、他のライブラリでもそんなに見かけないという実感があります(他言語から輸入したFP系ライブラリでは、案外あるかもしれませんが、未確認です)。

さらに余談ですが、Scala研修テキストでは、この辺について、


ただ、Scalaではカリー化のテクニックを使うことは、他の関数型言語に比べてあまり多くありません。


と書いてあります(他人事みたいなので、正確にいうと「書きました」ですが)。しかし、考えてみると、Scalaであんまり使わないカリー化を説明する必要なかったのでは疑惑があり、「関数のカリー化」という節自体を削った方が良いかなーという気がしています。

追記(2019/08/06):実は、コップ本含むScala公式方面の説明で、複数の引数リストを持つメソッドを定義することをカリー化と呼んでいることがあります。

たとえば、

https://docs.scala-lang.org/ja/tour/multiple-parameter-lists.html

のタイトルは「複数パラメータリスト(カリー化)」です。コップ本では、タイトルだけでなく、本文でも、カリー化に関して紛らわしい記述があるので注意が必要です。たとえば、上記URLにある

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

は、直接的には (z: B)(op: (B, A) => B)という複数の引数リスト(parameter list)を持つメソッドとして規定されていますし、言語仕様上もそう(言語仕様上カリー化という用語は出てこない)なのですが、コップ本では、このようなメソッド定義について、カリー化という概念を使って説明しています。複数の引数リストというものをカリー化という概念で説明するのが間違いとは言い切れないところはあるのですが、プログラミング言語に依存しないカリー化とはまた別ですので、混同しないように注意するのが良いです。

関連ツイート