関数を作るという感覚
今回のここまでの議論は、BMI値を計算する関数bmicから始まっているが、よくよく振り返ると、どのようにBMI値を計算するかという内容にはまったく触れていない。最後のfun2Funに至っては、具体的な型にさえも触れていない。
関数の型シグネチャや、束縛の等式の形式を議論していたのである。それでも、「1つの値を導くのに2つのパラメータを含むペア型の引数を取る関数を、同じ1つの値を導くのに2回の関数適用を行う関数に変換する関数」を導き出せた。
bmicは型からいっても、目的からいっても、身長値から「体重値からBMI値への関数」への関数であった。bmicを身長値の具体的値に適用すると、関数が得られる。これは計算の実行時に関数を作ることだと考えられる。
Haskellプログラミングでは、このように関数を作る関数を定義することを考えるのが普通であり、それが関数型言語と称される所以(ゆえん)である。関数適用によって単なるデータを作るというのではなく、関数を作るという感覚を手に入れると、Haskellプログラミングがずっと身近なものになるはずである。最初のうちはこのことを意識するようにすると上達が早くなる。
新しいデータ型を定義する
bmiはBMI値を計算するものだった。しかし、これを実際に使う場面を考えるとBMI値そのものよりも、BMI値のカテゴリを表示してもらえると便利である。そこでまず、BMIのカテゴリを表すデータを定義しよう。世界保健機構(WHO)によるカテゴリは以下のようになっている。
カテゴリ | BMI値 |
---|---|
Underweight(やせ) | < 18.5 |
Normal(標準) | < 25.0 |
Overweight(太り気味) | < 30.0 |
Obese(肥満) | ≧ 30.0 |
そこでBMIのカテゴリに対応するデータを以下のように定義する
data BMICategory = Underweight | Normal | Overweight | Obese |
これは、BMICategoryという名前の型を新たに定義する書き方である。BMICategoryは、Underweight、Normal、Overweight、Obeseという4つの値を持つ型である。BMICategoryは型構成子であり、Underweight、Normal、Overweight、Obeseはデータ構成子である(値構成子ともいう)。データ構成子は式であるが、型構成子は式ではない。型構成子は型シグネチャの中にしか現れない。
BMI値からBMIカテゴリへの変換関数は以下のように定義できる。
classify :: BMI -> BMICategory |
上の定義の中で、| b < 18.5の部分をガード節という。ガード節の始まりを示す|記号と定義の左右を分ける=記号との間にはBool型の値を表す式を書く。関数classifyが具体的なBMI値に適用されると、ガード式を上から順に検査して最初にTrueを返したガードの右辺が、その関数適用の結果の値として採用される。例えば、BMI値が27.2の人なら、
*BMI> classify 27.2 |
というわけで「太り気味」ということになる。前回、身長は固定して体重の変化のみでBMI値を測る関数を定義することを考えた。身長171cm(1.71m)の人用の関数myBMIをもう一度定義する。
myBMI :: Weight -> BMI |
これが正しい定義であることを確かめるのは宿題にしておく。myBMIは体重値からBMI値への変換関数であるが、BMI値そのものではなく、BMIカテゴリが欲しい場合どうするか。以下のような型シグネチャを持つ関数myWeightClassをどう定義するか。
myWeightClass :: Weight -> BMICategory |
これは、myBMIを具体的なWeight値に適用し、具体的なBMI値を得たら、それにclassifyを適用すればよい。
myWeightClass w = classify (myBMI w) |
実際に使ってみると、
*BMI> myBMICategory 79.5 |
ダイエットが必要かもしれない。
myWeightClassの考え方は別にもあって、例えば、「myWeightClassという変換は、myBMIという変換とclassifyという変換をこの順で続けて行うことに等しい」と考えることができる。
すなわち、2つの変換すなわち関数を合成した関数と考えるわけである。2つの関数を合成するにはPreludeで定義されている.演算子を使う。そうすると、myWeightClassの定義は、
myWeightClass = classify . myBMI |
と書ける。.は右側の被演算子の関数による変換を施してから、その結果に左側の被演算子の関数による変換を施すような関数を作る演算子である。
bmicを、curryをbmiに適用して作ったように、myWeightClassを、.をclassifyとmyBMIに適用して作ったわけである。この2つの定義では定義する関数の引数については何にも言及していない。関数を関数に関数で変換するというスタイルはHaskellプログラミングではよく使う。
今回は予定を変更して関数の定義や考え方について追加で説明した。特に関数によって関数を作るというHaskellらしい考え方について説明したつもりである。Haskellらしいプログラミングが身に付いてくるまでは、これらのスタイルを意識するようにすると理解が進み、上達が早くなる。
次回は、再帰について説明する予定である。
3/3 |
Index | |
もう少し関数の話をしよう | |
Page1 関数適用 関数の型 |
|
Page2 関数束縛 |
|
Page3 関数を作るという感覚 |
のんびり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を紹介する。※ショートカットキー、アクセスキーの解説あり
|
|