カリー化
自分のBMIの変化を観察したい。成人であれば身長は変化しないので、BMI値は体重にのみ依存する。
例えば、身長1.71メートルの人専用の関数は以下のように定義できる。
bmi171 :: Weight -> BMI |
身長1.71メートルの人であれば、bmi171を使えば、体重の変化を、BMIの変化として見ることができる。身長1.59メートルの人用なら、
bmi159 :: Weight -> BMI |
というわけである。ここで、bmi171やbmi159を生成する関数というのを考えてみよう。bmil171やbmi159の定義を関数抽象の表現で書くと、
bmi171 = \ w -> bmi (1,71, 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 |
となる。
実際に使ってみると、
*BMI> bmi171 79.5 |
となる。
関数適用は式を2つ並べて表現し、1つ目の式が関数の値を表し、2つ目の式が引数の値を表す。従って、以下のようにbmicを使うこともできる。
*BMI> (bmic 1.71) 79.5 |
(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 |
関数の型シグネチャは、〈入力の型〉->〈出力の型〉という形式なので、
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) |
従って、bmicは、
bmic = curry bmi |
と定義できる。
*BMI> (curry bmi) 1.71 79.5 |
関数の型シグネチャ
関数の型シグネチャは、関数の入力値の型と出力値の型を宣言するものなので、関数名と型シグネチャを読むと、その関数のおよその働きを想像することができる。つまり、型シグネチャはプログラマが自分の意図を表現するために書くものである。従って、型シグネチャを読む人が振る舞いを想像しやすい名前を使うことが重要である。
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の世界を体験してみよう |
|
- プログラムの実行はどのようにして行われるのか、Linuxカーネルのコードから探る (2017/7/20)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。最終回は、Linuxカーネルの中では、プログラムの起動時にはどのような処理が行われているのかを探る - エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね? (2017/7/13)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、プログラムの終わりに呼び出されるexit()の中身を探る - VBAにおけるFileDialog操作の基本&ドライブの空き容量、ファイルのサイズやタイムスタンプの取得方法 (2017/7/10)
指定したドライブの空き容量、ファイルのタイムスタンプや属性を取得する方法、FileDialog/エクスプローラー操作の基本を紹介します - さらば残業! 面倒くさいエクセル業務を楽にする「Excel VBA」とは (2017/7/6)
日頃発生する“面倒くさい業務”。簡単なプログラミングで効率化できる可能性がある。本稿では、業務で使うことが多い「Microsoft Excel」で使えるVBAを紹介する。※ショートカットキー、アクセスキーの解説あり
|
|