bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

ガタガタ(型々)とうるさい複素数

1.分野向けの型を作成する

Haskellでは、整数や小数などの一般的な型は提供しているが、それぞれの応用分野で用いられる型は用意してない。電気・電子の分野では複素数で表すと便利な時があるが、Haskellでは複素数は残念ながら用意していない。

ここでは、複素数の型を定義するとともに、複素数に関わる演算も定義することを考える。

一般に、新しい型を作成するときは、型を作成するために用意されているdataの後に、型コンストラクタを与える。型コンストラクタの後に続くのは、型引数である。型引数は他の言語ではあまり見ることのないHskellを特徴づける機能の一つである。型引数により、このデータ型がどのような型を利用しているのかを明示する。(場合によっては明示しなくてもよい場合があるが、今回の例はそうではない。)

ここでは、複素数の型コンストラクタはComplexNumberとする。

複素数は、数学では、\(a + ib\)と記述される。\(a\)を実数部の値と呼び、\(b\)を虚数部の値と呼ぶ。複素数は、この\(a\)と\(b\)を用いて表すことにするが、\(a\)と\(b\)とも同じ種類の数(例えば小数)なので、これは一つの型引数で表すことができる。これをaとすると型コンストラクタの部分は以下のようになる。

data ComplexNumber a

型コンストラクタができたので、次にデータの作り方を示す値コンストラクタの部分を示す。紛らわしいのだが、(通常、よく行われているので、)値コンストラクタの名前も型コンストラクタと同じものをここでは用いる。また、複素数は実数部と虚数部を有するので、それぞれ型引数aを用いて表す。値コンストラクタは次のようになる。

ComplexNumber a a

型コンストラクタと値コンストラクタを「=」でつなぐと、複素数の型定義は終了である。次のようになる。

data ComplexNumber a = ComplexNumber a a deriving (Show)

なお、deriving (Show)はComplexNumberを表示可能とするための指示である。

2.演算を定義する

複素数のデータ型が定義できたので、複素数の演算を定義する。複素数の演算は次のようになっている。
\begin{eqnarray*} (a + ib) + (a’ + ib’)& = & (a + a’) + i(b + b’) \\ (a + ib) - (a’ + ib’) & = & (a - a’) + i(b - b’) \\ (a + ib) * (a’ + ib’) & = & (a * a’- b * b’) + i(a * b’ + b * a’) \\ (a + ib) / (a’ + ib’) & = & (a * a’+ b * b’) / div + i(a * b’ - b * a’) / div \\ where\: div & = & a’ * a’ + b’ * b’\end{eqnarray*}

上記の式を素直にHaskellで表せばよいのだが、実数部と饗数部でどのような数を利用するのかを型シグネチャで示す必要がある。加算、減算、乗算については、整数同士で、あるいは、小数同士での演算の両方を可能にしておいた方が、便利であるので、取りうる型はNumとする。但し、除算については、小数になるので、取りうる型はFractionalとする。

cPlus :: (Num a) => ComplexNumber a -> ComplexNumber a -> ComplexNumber a
(ComplexNumber a b) `cPlus`  (ComplexNumber a' b') = ComplexNumber (a + a') (b + b')

cMinus :: (Num a) => ComplexNumber a -> ComplexNumber a -> ComplexNumber a
(ComplexNumber a b) `cMinus`  (ComplexNumber a' b') = ComplexNumber (a - a') (b - b')

cMultiply :: (Num a) => ComplexNumber a -> ComplexNumber a -> ComplexNumber a
(ComplexNumber a b) `cMultiply`  (ComplexNumber a' b') = ComplexNumber (a * a' - b * b') (a * b' + a' * b)

cDivide :: (Fractional a) => ComplexNumber a -> ComplexNumber a -> ComplexNumber a
(ComplexNumber a b) `cDivide`  (ComplexNumber a' b') = ComplexNumber ((a * a' + b * b') / div)  ((a * b' - a' * b) / div)
  where
    div = a' * a' + b' * b'

なお、cPlus, cMinus, cMultiply, cdivideはそれぞれ複素数の加算、減算、乗算、除算である。これらを中置関数として用いる場合には、両端を「`」で囲む必要がある。なお、「’」でないことに注意してほしい。cPlusの演算だけ簡単に説明する。型シグネチャでは型引数aを用意し、それが取りえる型はNumであると指定している。さらに、cPlusはComplexNumberを二つ入力し、ComplexNumberを出力すると型シグネチャの部分で定義している。その後、cPlusの関数の定義をしているが、これは複素数の加算の定義をそのまま記述したものである。

3.実行結果

上記のプログラムを実行した結果は次のとおりである。
f:id:bitterharvest:20141003104100p:plain

出来上がったプログラムを利用して、整数と小数を混ぜ合わせて計算しようとするとどのようなことが起こるか観察して欲しい。Haskellが型にうるさい言語であることが分かる。