F#での関数引数
F#の関数は常に1つの引数しか持ちません。
→誤りでした
F#での関数のプリミティブな形式は次のようになっていて(F# 4.1 spec 6.9.12)
F#は内部的にはアリティ(引数の数のこと)も管理しています(F# 4.1 spec 14.11)
(fun v1 … vn -> expr)
ただ、F#を使う人から見たときには
F#の関数はすべて、
1引数の関数を組み合わせて出来ているかのように扱うことが出来ます。
つまり、次のようにみなせます
複数引数の関数
//この関数は
let f a b c = a+b+c;;
//この関数と同じ意味として扱える
let ff =
fun a ->
fun b ->
fun c ->
a+b+c;;
この上のケースから下のケースへの変換のことを
カリー化と呼びます
ちなみにlispでは、
(lambda (x y z) ...) ;(1)
(lambda (x) (lambda (y z) ..)) ;(2)
(lambda (x) (lambda (y) (lambda (z) ..))) ;(3)
(1)〜(3)は違うものになります。
(1)に引数xを与えて(lambda (y z) ..)を得る事を部分適用と呼び
(1)から(3)を作り出すことをカリー化と
呼び分ける場合もあるみたいです。
カリー化/Schonfinkel化
Wikipediaによるとカリー化の最初の考案者は
Moses Schonfinkelであり、その後 Haskell Curryによって再発見されたとあります。
そのため、Schonfinkel化(
Schonfinkelisation。シューフィンケル?)という用語が提案(※1)されているそうです。
Schonfinkel氏は、コンビネータ理論関連に貢献したロシアの数学者だそうです。
上記サイトよると、
f:(X × Y) → Zをカリー化すれば
g: X → (Y → Z)という関数が得られると説明してあります。
F#では直積を表現するのにタプルが使えるので
それぞれF#の型で表記すると
val f : 'X * 'Y -> 'Z
val g:'X -> 'Y -> 'Z
となります。
これをコードとして書くと
カリー化/Schonfinkel化
> let f(x,y)=x+y;;
val f : int * int -> int
> let curry f x y = f(x,y);;
val curry : ('a * 'b -> 'c) -> 'a -> 'b -> 'c
> curry f;;
val it : (int -> int -> int) = <fun:it@26-13>
このようになります。
また、これの逆変換(数学的にはカリー化の双対になるそうです)を
アンカリー化(uncurrying)といいます。
カリー化/Schonfinkel化
> let g x y = x+y;;
val g : int -> int -> int
> let uncurry f (x,y) = f x y;;
val uncurry : ('a -> 'b -> 'c) -> 'a * 'b -> 'c
> uncurry g;;
val it : (int * int -> int) = <fun:it@36-15>
具体的なコード
最後にインタプリタを用いて
カリー化の様子をコメントしたコードを載せます。
カリー化
//足し算する関数の定義
> let add a b = a + b;;
val add : int -> int -> int
//普通に足し算を実行
> add 1 2;;
val it : int = 3
//カリー化。型がint -> intである関数が返っているのがわかります。
> add 1;;
val it : (int -> int) = <fun:clo@5>
//もちろん、他の関数の定義に使うのもOK
> let inc1 = add 1;;
val inc1 : (int -> int)
> inc1 3;;
val it : int = 4
//組み込みの演算子もカリー化できる。inc1と同じ処理をする関数
> let inc1_2 = ((+)1);;
val inc1_2 : (int -> int)
> inc1_2 3;;
val it : int = 4
//今度は引数3個
> let add3 a b c = a + b + c;;
val add3 : int -> int -> int -> int
//カリー化。最初の引数を固定
> add3 1;;
val it : (int -> int -> int) = <fun:clo@11>
//カリー化。引数を2個指定
> add3 1 2;;
val it : (int -> int) = <fun:clo@12>
このように、カリー化を用いると簡単に新しい関数が定義出来ます。
この時のポイントは「最初の引数が固定される」という点で
カリー化を意識して関数を作るなら
引数の順番も意識すると良いかもしれません。
※1 I. Heim and A. Kratzer (1998). Semantics in Generative Grammar. Blackwell.