Scalaのカリー化についての脳内会議の議事録

カリー化についてtwitterにぐちってたら、ymsr先生から
http://mamezou.net/mamenight/documents/mamenight030/Scala080226hanyuda.ppt
を紹介された。


経緯的なもの

nobeans> なるほど、Groovyはいい感じだけど、Scalaのはカリー化とは違う気がするなぁ。def f (x:int, y:int, z:int)を def f(x:int)(y:int)(z:int)的に扱ってくれればいいのに。 http://tinyurl.com/6qv ...
nobeans> 引数を減らした別メソッドを定義して、フル引数なメソッドに委譲する手間と大してかわらないような。クロージャが絡まないとあまり意味がない?もしかしてカリー化って言うより単に関数の評価順序の制御に使うための記法なのかな? <Scalaのカリー化
yamashiro @nobeans たとえば引数が 5 個あった場合に、メソッドの組み合わせって 5 の階乗分、 120 通りあるから、その場合、委譲するメソッドを全て書くのはありえないですよね。まぁ、使う分だけ委譲メソッド書くにしても、書かなくていいものは書きたくないんじゃないかな。
nobeans> @yamashiro あ、文字数オーバできれちゃったみたい。つ 「GroovyとScalaでのカリー化 http://d.hatena.ne.jp/fits/20080304/1204647309
nobeans> @yamashiro カリー化って引数の前のほうから束縛していくものだと思ってるので、引数5個なら、5通りかなと。
yamashiro> @nobeans scala のカリー化は引数を前から束縛しないでもできるので、 ( 引数の個数 -1) 階乗分パターンがありえますね(今試してみた)
nobeans> @yamashiro コードのイメージがわかない...。つまり、他言語のカリー化と比べて、束縛対象を任意に設定可能って意味ではメリットがある。ただし、あらかじめ引数定義をグルーピングしておく必要がある。ってことですかね?
yamashiro> @nobeans http://mamezou.net/mamenigh...  のスライドの 36 枚目参照

ってわけで、資料の該当ページを読んでみた。


http://mamezou.net/mamenight/documents/mamenight030/Scala080226hanyuda.pptの36ページ

カリー化(関数の部分的具体化)

   1. def foo(x: Int)(y: Int) = x + y
   2. def add1(x:Int) = foo(x)(1) // x + 1に相当
   3. add1(100) ? 101
   4. def `100add`(y:Int) = foo(100)(y) // 100+yに相当
   5. `100add`(7) ? 107


これを読んで、思ったこと。

def foo(x: Int)(y: Int) = x + y
def add1(x:Int) = foo(x)(1)

def foo(x: Int, y: Int) = x + y
def add1(x:Int) = foo(x, 1)

の実際的な違いがあまりわからない....。嬉しさというか。


この例だったら正直どっちでもええやんとか思う。


カリー化って、一部の変数に値を束縛した中間関数みたいなものを作り出せるってところがメリットだと思ってるので、上の例みたいに関数定義が必要になるんだったら、何もカリー化なんて使わなくてもいいし、従来どおりのオーバーロードメソッド定義でも手数一緒じゃん?と。


もし、

val list = List(1, 2, 3)  // listってなによ?なので追記
list map foo(_)(1)  // 脳内コード

みたいにインラインでカリー化して使えるならわからないでもない。


んー、でも、

val list = List(1, 2, 3)  // listってなによ?なので追記
list map foo(_, 1)  // 脳内コード

って記法が許されればいいだけか。カリー化持ち出すまでもなく、単なる無名引数が使えるってだけで十分だなぁ。

無名引数っていう便利なものを導入した時点で、Scalaにおけるカリー化はワリとどうでもいい感じになるんかな。


実際に上みたいにかけるのか試したいんだけど、家のPCでscalaインタプリタがまともに動かない(応答がない)ので、試せない><


というわけで、あくまで脳内一人会議のレベルで申し訳ないです><

追記: 20:00くらい

まえがき

twitterでkmizuさんから

kmizu> @nobeans Scala のカリー化については、 def add(x :Int)(y :Int) = x + y; と定義してやって、 list map add(3) とかやってみると良いかと。

というリプライをもらって、上の議事録じゃ元々の疑問がかかれてないなと気づきました。

ってことで追記。

kmizuさんのお題について
def add(x :Int)(y :Int) = x + y
val list = List(1, 2, 3)  // listってなによ?なので追記
list map add(3) 

とやった場合、変数xが3に束縛されて、add(3)がカリー化された関数を返しますね。
それは直接的に書けば

def f(y: Int) = 3 + y

に相当する関数です。


というのはワリと普通のカリー化なので疑問は特にないです。


普通といってるのは、

  • 最初の引数から順番に部分束縛していく
  • インラインでカリー化して、戻り値の関数を直接別の高階関数に渡せる

みたいな意味で。


ですが、Scalaのカリー化について色々みていくと、

  • 関数定義であらかじめ仕込が必要 (括弧をわけておくという)
  • 引数の順番によらず任意の箇所を束縛するカリー化ができるらしい
    • けど、束縛しない変数をそのまま残すためには関数定義が必要?
  • あれ、インラインで書きたいなら、カリー化しなくても無名引数使えば簡単?

とか、色々疑問が募っていったわけです。

元々の疑問点

そもそも最初に何が引っかかったかというと、カリー化するためにはもとの関数定義で

def add(x :Int)(y :Int) = x + y
val list = List(1, 2, 3)  // listってなによ?なので追記
list map add(3) 

とカリー化する単位で括弧を分けて引数を定義しなきゃいけないよってところ。


なんで、こうかけないの?と。

def add(x :Int, y :Int) = x + y
val list = List(1, 2, 3)  // listってなによ?なので追記
list map add(3) // xを3に束縛

指定の方法だとなんか普通に2引数で呼び出すときも

def add(x :Int)(y :Int) = x + y
add(3, 4) // NG
add(3)(4) // OK

ってことになるらしいじゃないですか。(試せてないので、ここも脳内)

それって、つまり、

  • 普通の関数をカリー化するのはできない
  • カリー化しようと思って引数定義を括弧わけしておいた関数だけカリー化できる
  • カリー化は関数定義で同じ括弧にした引数グループ単位でしかできない
  • カリー化対応関数は、普通に全引数を束縛するときに、カッコ悪い

ってことじゃないですか。

なんか他の言語のカリー化と違ってビミョウな感じだなぁ。


ってのがスタートラインでした。

今のところ

↓この辺りが理解のヒントかなと思ってるんですが...。

カリー化されたメソッドの定義
* def open(file: String) (p: InputStream => Unit) { // fileは文字列型、pはクロージャ
* 使うときは open("foo.txt") {in => ... }
o openに "foo.txt"を渡し、その結果にクロージャを渡している
Route 477 - accesskeyを潰した , Scala勉強会@岡山-1 行ってきた , Scala勉強会@岡山-1 ログ , 一人暮らしを始めてから買ったもの