カリー化

プログラミングHaskell」読んでます。

カリー化(の読み方)に慣れてなくて読み進めるのが厳しいのでちょっと落ち着いてみた。

fooFunc :: Int → Int → Int

みたいなのが出てくると「えーっと……これが…………」となって固まる感じだったけど、
これはカジュアル(あくまでカジュアル)に読むなら

fooFunc :: (Int, Int) → Int

みたいなことで、要するに「引数は Int が 2個で返り値の型は Int」と読めばよいと分かった。
分かってしまえばなんてことないですね。

もっと増えても同じで、

fooFunc :: Int → Int → Int → Int → Int → Int

fooFunc :: (Int, Int, Int, Int, Int) → Int

(引数は Int が 5個で返り値の型は Int)のように読めばいい。



で、カジュアルでない理解としては、

fooFunc :: Int → Int → Int

fooFunc :: Int → (Int → Int)

Int が引数(の型)で (Int → Int) が返り値(の型)。
で、これは何なのか。

便宜的に添字を付けて

fooFunc :: Int1 → (Int2 → Int3)

たとえば足し算を行う add という関数を作って 1 + 2 という計算をするとして、

  1. 1つ目の引数(Int1)を受け取って、Int2 → Int3 な関数を返す
  2. その関数に2つ目の引数(Int2)を渡して実行すると足し算の答え(Int3)が得られる

という動作をする。



JavaScript で書いてみる。

function makePartialAdd(int1){
  return function(int2){
    return int1 + int2;
  }
}

var partialAdd = makePartialAdd(1);
partialAdd(2); //=> 3

まとめると

(function(int1){
  return function(int2){
    return int1 + int2;
  }
})(1)(2); //=> 3


そんなことして何が嬉しいんや、という点に関しては、、、
とりあえずカリー化によって何がどうなっているかを考えると、
たとえば普通なら

def add(a, b)
  a + b
end

と「引数を2つ取って何かを行う」処理をひとまとまりにして
1つの関数(Rubyだけど以下関数で統一)として定義するところを、
「引数を1つ取る関数の呼び出しの連続」に変形している。
そこには「引数を1つ取る関数」しか出てこない。

つまり、「引数を1つ取る関数」「引数を2つ取る関数」「引数を3つ取る関数」…「引数をnつ取る関数」
が「引数を1つ取る関数の組み合わせ」という、1段階抽象度の高い形で表現できると。

car, cdr, cons, atom, eq があれば何でもできるぜヒャッハーみたいな……いやちょっと違うか……。

じゃなくて、それよりは「あっ、こうすれば再帰で書けるぞ」の感覚に似てるような。



カリー化を知らない状態だと

def add(a, b)
  a + b
end

という関数は「単純すぎてこれ以上分解・還元できないんじゃね?」と思えてしまうけれど、実はできる、というのは面白い。



ともあれ、これで続きが読める!


Haskell的には

fooFunc :: (Int, Int) → Int
barFunc :: Int → Int → Int

という2つの関数は関数の型が異なっていて、上記の繰り返しになるけれど次の2つのように違う。たぶん。

function fooFunc(int1, int2){
  return int1 + int2;
}
fooFunc(1, 2); //=> 3

function barFunc (int1){
  return function(int2){
    return int1 + int2;
  }
};
barFunc(1)(2); //=> 3