複数の引数の渡し方(カリー化、部分適用について)
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