平面上の線分

今日は、SICPの問題2.2を解いてみます。

問題は、線分を表現するデータ構造を作り、その線分の中間点を求めよという問題。データ構造は、x座標と、y座標を表すデータ構造を作って、ポイントを表すデータ構造を作れとなっているんだけど、そこは割愛。そのかわり、線分の長さを求める関数も作ることにします。

で、線分と座標を表すデータ構造は、このような感じ。

data Point = Point Integer Integer

instance Show Point where
    show (Point x y ) = "( "++show(x)++" , " ++ show(y)++" )"

data Line = Line Point Point

instance Show Line where
    show (Line s e) = show s ++ " - " ++ show e

makeLine:: Integer->Integer->Integer->Integer->Line
makeLine x y x' y' = Line (Point x y) (Point x' y')

毎回、Lineを作るのに、Pointとかうつのが面倒臭そうなので、makeLineなる関数も定義 。
中間点と、長さをあらわす関数は、こちら。

getMiddlePoint::Line->Point
getMiddlePoint (Line (Point x y) (Point x' y'))
        = Point ( calMiddle x x' ) (calMiddle y y')

calMiddle::Integer->Integer->Integer
calMiddle x x' = quot (x+x') 2

getLength::Line->Float getLength (Line (Point x y) (Point x' y'))
        = sqrt   ((func x x') + (func y y'))
        where
func x x' = (x'-x) ^ 2

関数funcは、自分のボキャブラリーから適切な名前が浮かばなかったので、こんな名前に。では、実行してみます。

$ ghci calline.hs
calline.hs:22:10:
No instance for (Floating Integer)
arising from use of `sqrt' at calline.hs:22:10-13
Probable fix: add an instance declaration for (Floating Integer)
In the definition of `getLength':
getLength (Line (Point x y) (Point x' y')) = sqrt ((func x x') + (func y y'))
Failed, modules loaded: none.

どうやら、(func x x') + (func y y')の返す値の型と、sqrtの引数の型があわないよう です。
(func x x') + (func y y') は、Integerだとおもわれます。sqrtの型は、

Prelude> :t sqrt
sqrt :: (Floating a) => a -> a

Float型みたいです。どうやら、IntegerはFloatには、勝手にならないようです。
ということで、ここではfromInteger関数を使ってみることにします。

Prelude> :t fromInteger
fromInteger :: (Num a) => Integer -> a

fromIntegerは、Integer型の値をNum型に直してくれる関数です。
Num型は、数値の規定クラスです。Javaをやっている人には、数値のなかでのObjectクラ スとでも言えば、わかりやすいでしょうか?rubyでいうとこのNumericです。
なので、Floatは、Numであるともいえるので、この関数を適用することで、今回の問題を解決できます。
以下に、完全なソースを

data Point = Point Integer Integer

instance Show Point where
    show (Point x y ) = "( "++show(x)++" , " ++ show(y)++" )"

data Line = Line Point Point

instance Show Line where
    show (Line s e) = show s ++ " - " ++ show e

makeLine:: Integer->Integer->Integer->Integer->Line
makeLine x y x' y' = Line (Point x y) (Point x' y')

getMiddlePoint::Line->Point
getMiddlePoint (Line (Point x y) (Point x' y'))
        = Point ( calMiddle x x' ) (calMiddle y y')

calMiddle::Integer->Integer->Integer
calMiddle x x' = quot (x+x') 2

getLength::Line->Float
getLength (Line (Point x y) (Point x' y'))
        = sqrt $ fromInteger  ((func x x') + (func y y'))
        where
                func x x' = (x'-x) ^ 2

では、実行結果です。

$ ghci calline.hs
Loading package base-1.0 ... linking ... done.
Compiling Main             ( calline.hs, interpreted )
Ok, modules loaded: Main.
*Main> makeLine 2 4 2 12
( 2 , 4 ) - ( 2 , 12 )
*Main> let linea = makeLine 2 4 2 12
*Main> getMiddlePoint linea
( 2 , 8 )
*Main> getLength linea
8.0

途中ででてくる、letは、変数に値を束縛することができます。なので、lineaは、makeLine 2 4 2 12の値に束縛されることになります。

追記:id:nobsunさんから指摘。letは関数ではないとのこと。ふつけるを読んで、letが式だったことを確認。すみませんでした。