第2回 関数の話をしよう

山下 伸夫
株式会社タイムインターメディア

2008/11/28

カリー化

 自分のBMIの変化を観察したい。成人であれば身長は変化しないので、BMI値は体重にのみ依存する。

 例えば、身長1.71メートルの人専用の関数は以下のように定義できる。

bmi171 :: Weight -> BMI
bmi171    w      =  bmi (1.71, w)

 身長1.71メートルの人であれば、bmi171を使えば、体重の変化を、BMIの変化として見ることができる。身長1.59メートルの人用なら、

bmi159 :: Weight -> BMI
bmi159    w      =  bmi (1.59, w)

というわけである。ここで、bmi171やbmi159を生成する関数というのを考えてみよう。bmil171やbmi159の定義を関数抽象の表現で書くと、

bmi171 = \ w -> bmi (1,71, w)
bmi159 = \ w -> bmi (1.59, w)

となる。この2つの関数定義(右辺)の違いは、1.71と1.59の部分、すなわち〈身長の値〉の部分である。成人(身長一定)の人のBMIを計算する関数のパターンを穴を使って表現すると、

\ w -> bmi (〈?〉, w)

ということになる。〈身長の値〉をパラメータとして、成人のBMIを計算する関数を得る関数bmicを考えるとその型は、

bmic :: Height -> ?

となる。?の部分の型はbmi171やbmi159の型であるから、Weight -> BMIである。すなわち、

bmic :: Height -> (Weight -> BMI)

である。値の定義は、穴を表す変数をhとして、

bmic = \ h -> (\ w -> bmi (h, w))

あるいは、

bmic h = \ w -> bmi (h, w)

となる。これを使ってbmi171やbmi159を定義し直すと、

bmi171 = bmic 1.71
bmi159 = bmic 1.59

となる。

 実際に使ってみると、

*BMI> bmi171 79.5
27.1878526726172
*BMI> bmi159 73.0
28.875440053795337

となる。

 関数適用は式を2つ並べて表現し、1つ目の式が関数の値を表し、2つ目の式が引数の値を表す。従って、以下のようにbmicを使うこともできる。

*BMI> (bmic 1.71) 79.5
27.1878526726172
*BMI> (bmic 1.59) 73.0
28.875440053795337

 (bmic 1.71) 79.5は関数適用bmic 1.71の結果の値(すなわち関数)を実引数に79.5に適用するという意味である。関数適用が2回行われているわけである。Haskellでは関数適用は左結合性があるので、(bmic 1.71) 79.5は括弧を省略してbmic 1.71 79.5と書いてもよい。Haskellでは後者の書き方をするのが普通である。

*BMI> bmic 1.71 79.5
27.1878526726172
*BMI> bmic 1.59 73.0
28.875440053795337

 関数の型シグネチャは、〈入力の型〉->〈出力の型〉という形式なので、

bmic :: Height -> (Weight -> BMI)

であれば、入力の型はHeightであり、出力の型は、Weight -> BMIという関数の型である。

 Haskellの型シグネチャ中では->はある種の中置演算子であると見なす。さらに->には右合性があるものと見なすので、Height -> (Weight -> BMI)は括弧を省略して、Height -> Weight -> BMIと書いてもよい。この型シグネチャだけを見ると、最後の型BMIの値を得るのに、Height型の値とWeight型の値が必要なのが分かる。

 しかし、これは2つの引数に関数bmiを1回だけ適用して得るわけではなく、2回の関数適用を経て得られることに注意が必要である。すなわち、bmicは2引数関数ではなく、あくまで1引数関数である(とはいうものの、bmicを2引数関数ということもよくある)。

 ここまでで見たように、BMI値はその値が2つのパラメータに依存する。このような値を求める関数は、直截(ちょくせつ)に2つのパラメータを含む引数(ペア型)を取る関数bmiでもよいし、2回の関数適用を使うbmicでもよい。一方のパラメータを固定した関数を作りたいときにはbmicの方が柔軟で便利である。

 2つのパラメータを含む引数を取る関数を、2回の関数適用を行う関数に変換することをカリー化(currying)という。

カリー化という用語は、論理学者Haskell B. Curryに由来する。もちろん、プログラミング言語Haskellの名前も、この論理学者の名前に由来する。

 Haskellにはこの変換を行う関数、および逆変換を行う関数が標準で提供されている。

curry   :: ((a, b) -> c) -> (a -> b -> c)
uncurry :: (a -> b -> c) -> ((a, b) -> c)

 従って、bmicは、

bmic = curry bmi

と定義できる。

*BMI> (curry bmi) 1.71 79.5
27.18785267261722
*BMI> curry bmi 1.71 79.5
27.18785267261722

関数の型シグネチャ

 関数の型シグネチャは、関数の入力値の型と出力値の型を宣言するものなので、関数名と型シグネチャを読むと、その関数のおよその働きを想像することができる。つまり、型シグネチャはプログラマが自分の意図を表現するために書くものである。従って、型シグネチャを読む人が振る舞いを想像しやすい名前を使うことが重要である。

 Haskellの処理系はスクリプトの実行に先立って、それぞれの変数の定義コードから、その束縛されている値の型を推論する。それから、推論した型とプログラマが書いた型シグネチャが矛盾しないかどうかをチェックする。もし、矛盾があれば型エラーとなって、スクリプトは実行できない。

 型シグネチャはプログラマの意図を書くものであり、型推論はその意図とコードとの間に齟齬(そご)がないかどうかをチェックするために存在する。

◆ ◇ ◆

 今回は、

  • 関数抽象
  • 関数適用
  • カリー化

について説明した。これらの概念は少し抽象的だが、Haskellプログラミングでは本質的な概念である。また、Haskellプログラムのコードとして直接現れ計算対象となるので重要である。

 また、型シグネチャの読み方と意味についても説明した。次回は「再帰」と「代数型」について説明する。

2/2
 

Index
関数の話をしよう
  Page1
関数抽象と関数適用
ペア(2つ組)
Page2
カリー化
関数の型シグネチャ
のんびりHaskell

 Coding Edgeお勧め記事
いまさらアルゴリズムを学ぶ意味
コーディングに役立つ! アルゴリズムの基本(1)
 コンピュータに「3の倍数と3の付く数字」を判断させるにはどうしたらいいか。発想力を鍛えよう
Zope 3の魅力に迫る
Zope 3とは何ぞや?(1)
 Pythonで書かれたWebアプリケーションフレームワーク「Zope 3」。ほかのソフトウェアとは一体何が違っているのか?
貧弱環境プログラミングのススメ
柴田 淳のコーディング天国
 高性能なIT機器に囲まれた環境でコンピュータの動作原理に触れることは可能だろうか。貧弱なPC上にビットマップの直線をどうやって引く?
Haskellプログラミングの楽しみ方
のんびりHaskell(1)
 関数型言語に分類されるHaskell。C言語などの手続き型言語とまったく異なるプログラミングの世界に踏み出してみよう
ちょっと変わったLisp入門
Gaucheでメタプログラミング(1)
 Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう
  Coding Edgeフォーラムフィード  2.01.00.91


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間