🐡

複数の引数の渡し方(カリー化、部分適用について)

2022/04/27に公開

Haskellの関数は引数を常に1つだけ受け取るようになっています。1引数化されたこの仕組みを難しい言葉でカリー化と呼びます。この記事では関数に対して複数の引数を渡した場合、内部ではどのような処理をするかを学びます。引数が2つの場合、3つの場合を例に挙げてみましょう。

引数が2つの場合

引数を2つ足し算するaddtwo関数を例に考えます。10、20という引数を2つ受け取っているのですが、関数は1つの引数しか受け取れないルールとなっています。この場合どのように処理されるのでしょうか。

addtwo :: Int -> Int -> Int
addtwo a b
    = a + b
    
main = print (addtwo 10 20)
-- 実行結果「30」

10、20という2つの引数へ、addtwo関数は以下のように処理を行います。(f(x)という関数が実際に返却されるワケではなくてイメージするための具体例です。)

(1)addtwo関数が10へ適用される
(2)数値を返却する代わりに、新たに別の関数を返却する
 (新たに作られたこの関数は引数を1つとって、10と足し算する)
(3)新たに作られた別の関数が20へ適用される
→10+20の計算結果として30が返却される

関数の呼び出しは左結合です。addtwo関数を呼び出す場合、addtwo 10 20(addtwo 10) 20と同じです。addtwo 10は続く引数に対して20を加える関数として成立しています。そして20を引数として(add 10)を呼び出しており、結果として20という値が得られます。

Haskellの関数は常に1つの引数しか取れません。引数を複数取るには、それ用にもう1つ新しく関数が作られる点に注目して下さい。

引数が3つの場合

引数が3つある場合も同じです。引数を3つ足し算するaddthree関数はどうでしょうか。

addthree :: Int -> Int -> Int -> Int
addthree a b c
    = a + b + c
    
main = print (addthree 10 20 30)

こちらも先ほどと流れは同じです。引数は1つずつ関数へ適用されていきます。

(1)addthree関数が10へ適用される
(2)数値を返却する代わりに、新たに別の関数を返却する
 (新たに作られたこの関数は引数を1つとって、10と足し算する)
(3)新たに作られた別の関数が20へ適用される
(4)数値を返却する代わりに、新たに別の関数を返却する
 (新たに作られたこの関数は引数を1つとって、10,20と足し算する)
(5)新たに作られた別の関数が30へ適用される
→10+20+30の計算結果として60が返却される

同じ関数は左結合なので、引数:10から順番に適用されていきます。((addtwo 10) 20) 30を呼び出しするのと全く同じ仕組みです。新たに別の関数を作るという点がイメージしづらいですが、図で一緒に理解してしまいましょう。

関数が返却されることを確認

数値を返却する代わりに、新たに別の関数を返却する..と説明してきました。では本当に関数が返却されるかどうか確かめてみます。あえて引数を1つだけ渡してみるとどうでしょうか。以下のコードを実行すると次のようなエラーが出ます。

addtwo :: Int -> Int -> Int
addtwo a b
    = a + b
    
main = print $ addtwo 10
エラー内容
No instance for (Show (Int -> Int)) arising from a use of ‘print’

No instance = データ型がありません と出ます。Int -> Intの関数を生成したけど、それをShow型クラスでは扱えない..という内容です。引数を1つだけ渡した時点で返却されるのは関数なので、それをprint関数で文字列にすることができないのです。

ここで注目してもらいたいのが「引数が足りないよ!」というエラーではない点です。あくまで「文字列として表現できないよ!」というエラーであって、引数が1つでも問題なく処理ができるのです。これによって新たに別の関数が返却されることを確認できました。

Discussion