第3回 もう少し関数の話をしよう

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

2009/1/14

関数を作るという感覚

 今回のここまでの議論は、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
  deriving (Show)

 これは、BMICategoryという名前の型を新たに定義する書き方である。BMICategoryは、Underweight、Normal、Overweight、Obeseという4つの値を持つ型である。BMICategoryは型構成子であり、Underweight、Normal、Overweight、Obeseはデータ構成子である(値構成子ともいう)。データ構成子は式であるが、型構成子は式ではない。型構成子は型シグネチャの中にしか現れない。

 BMI値からBMIカテゴリへの変換関数は以下のように定義できる。

classify :: BMI -> BMICategory
classify b | b < 18.5 = Underweight   -- やせ
           | b < 25.0 = Normal        -- 標準
           | b < 30.0 = Overweight    -- 太り気味
           | otherwise = Obese        -- 肥満

 上の定義の中で、| b < 18.5の部分をガード節という。ガード節の始まりを示す|記号と定義の左右を分ける=記号との間にはBool型の値を表す式を書く。関数classifyが具体的なBMI値に適用されると、ガード式を上から順に検査して最初にTrueを返したガードの右辺が、その関数適用の結果の値として採用される。例えば、BMI値が27.2の人なら、

*BMI> classify 27.2
Overweight

というわけで「太り気味」ということになる。前回、身長は固定して体重の変化のみでBMI値を測る関数を定義することを考えた。身長171cm(1.71m)の人用の関数myBMIをもう一度定義する。

myBMI :: Weight -> BMI
myBMI = curry bmi 1.71

 これが正しい定義であることを確かめるのは宿題にしておく。myBMIは体重値からBMI値への変換関数であるが、BMI値そのものではなく、BMIカテゴリが欲しい場合どうするか。以下のような型シグネチャを持つ関数myWeightClassをどう定義するか。

myWeightClass :: Weight -> BMICategory

 これは、myBMIを具体的なWeight値に適用し、具体的なBMI値を得たら、それにclassifyを適用すればよい。

myWeightClass    w      = classify  (myBMI w)

 実際に使ってみると、

*BMI> myBMICategory 79.5
Overweight

ダイエットが必要かもしれない。

 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の世界を体験してみよう
  Coding Edgeフォーラムフィード  2.01.00.91


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

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間