32.6 関数の定義

2次方程式“x2+2x+1=0”の解を計算させてみましょう.一般に,2次方程式“ax2+bx+c=0”の解は“(-b±√(b2-4ac))/2a”なので,解の1つは

(-2.0 +. sqrt(2.0 *. 2.0 -. 4.0 *. 1.0 *. 1.0)) /. (2.0 *. 1.0);;return2
- : float = -1.
のように求めることができます.

hwb32.3 小数とその計算で説明したように,OCamlでは小数どうしの計算は“2.0”のように小数点を付けて数値を書き,“+.”のようにピリオドの付いた演算子を使う約束でした.“sqrt”はhwb32.4 関数の利用で出てきた平方根を求める関数です.少し格好悪いのですが,ともかく求めることができました.

同様にして,“5x2+7x-4=0”であれば,

(-7.0 +. sqrt(7.0 *. 7.0 +. 4.0 *. 5.0 *. 4.0)) /. (2.0 *. 5.0);;return2
- : float = 0.43578166916005473
という具合です.

しかし,似たような長い式を何度も書くのは面倒ですし,間違いも起こしがちです.そこでa, b, cが与えられたら2次方程式“ax2+bx+c=0”の解を1つ“(-b+√(b2-4ac))/2a”を求める計算式に“quadratic1”という名前を付けてみましょう.

つまり,関数を自分で作成することになります.このように似たような処理を関数として定義して,それを何度も使うことで作業の量を減らすことができるのがプログラミングの利点の一つです.

関数の定義

OCamlで関数を作成し名前を付ける場合には,

let 関数名(引数名1, 引数名2, ...) = 式

のように書きます.

今回の“quadratic1”の場合は次のようになります.

let quadratic1(a, b, c) = (-. b +. sqrt(b *. b -. 4.0 *. a *. c)) /. (2.0 *. a);;return2
val quadratic1 : float * float * float -> float = <fun>

これでOCamlに“quadratic1”という名前の関数とその式を覚えさせることができます.このようにしてみると,関数は式の一部を後から決めることができる式だと言えます.この例では,a, b, cという3つの変数が使われている場所は後から値を決めることになります.

定義した関数の利用

それでは,関数として定義した解の公式を使って,“x2+2x+1=0”の解を求めてみましょう.

hwb32.4 関数の利用にある通り,関数を使う場合には,

関数名(引数1, 引数2, ...)

のように書きます.

quadratic1(1.0, 2.0, 1.0);;return2
- : float = -1.

関数を使うことで他の方程式の解を求める場合も簡単になりました.例えば“5x2+7x-4=0”の解を求めるのであれば次のように書くだけです.

quadratic1(5.0, 7.0, -4.0);;return2
- : float = 0.43578166916005473

2次方程式の解の片方を求める関数quadratic1を定義したら,もう片方の解“(-b-√(b2-4ac))/2a”を求めるquadratic2も定義してみましょう.

やり方としては,quadratic1の定義の中の“+.”を1ヶ所“-.”に書き換えるだけですから簡単そうです.しかしここでは,もう少し式を整理してから定義をやり直すことを考えます.

quadratic1の定義をもう一度見てみましょう.

let quadratic1(a, b, c) =
(-. b +. sqrt(b *. b -. 4.0 *. a *. c)) /. (2.0 *. a);;

まず,“b *. b”という部分は“b”の自乗を計算しています.自乗を計算していることが読みとれるように,“square”という関数を定義して,それで置き換えてみましょう.

let square(x) = x *. x;;return2
val square : float -> float = <fun>
let quadratic1(a, b, c) = (-. b +. sqrt(square(b) -. 4.0 *. a *. c)) /. (2.0 *. a);;return2
val quadratic1 : float * float * float -> float = <fun>

このように,自分が定義した関数“square”を,別の関数“quadratic1”の定義に使うことができます.OCamlの場合,“quadratic1”の中で“square”を使うのであれば,“square”は“quadratic1”より先に定義しておかなければいけません.

さらに,“sqrt”の内側の“square(b) -. 4.0 *. a *. c”という部分は判別式と呼ばれています.ここも別に定義してみましょう.

let discriminant(a, b, c) = square(b) -. 4.0 *. a *. c;;return2
val discriminant : float * float * float -> float = <fun>
let quadratic1(a, b, c) = (-. b +. sqrt(discriminant(a,b,c))) /. (2.0 *. a);;return2
val quadratic1 : float * float * float -> float = <fun>

discriminant は二次方程式の各項の係数a, b, cが与えられたら,その判別式“b2-4ac”を求める関数です.これを使ったquadratic1の定義は,前と比べてずいぶん簡単になりました.この定義でも,最初の定義と同じ結果になります.

quadratic1(1.0, 2.0, 1.0);;return2
- : float = -1.
quadratic1(5.0, 7.0, -4.0);;return2
- : float = 0.43578166916005473

さて,2次方程式のもう片方の解の計算でした.判別式を計算する関数がすでにあるので次のように定義して使うことができます.

let quadratic2(a, b, c) = (-. b -. sqrt(discriminant(a,b,c))) /. (2.0 *. a);;return2
val quadratic2 : float * float * float -> float = <fun>
quadratic2(1.0, 2.0, 1.0);;return2
- : float = -1.
quadratic2(5.0, 7.0, -4.0);;return2
- : float = -1.83578166916005481

このように,何度も行う計算や何らかの意味のある部分は関数にするとよいです.