cons,car,cdr

今日は、問題2.4をといてみます。

問題2.4は、lispのconsと、carを

(define (cons x y)
  (lambda (m) (m x y)))
(define (car z)
  (x (lambda (p q) p)))

と定義されているときのcdrはどう定義されるかと問題。

ということで、cons,car,cdrのこの定義で実装

cons x y = \x' -> x' x y
car x = x ( \x' y' -> x')
cdr x = x ( \x' y' -> y')

で、実行。

$ ghci cons.hs
*Main> :l cons.hs
Compiling Main             ( condcons.hs, interpreted )
Ok, modules loaded: Main.
*Main> :t cons
cons :: t -> t1 -> (t -> t1 -> t2) -> t2
*Main> :t car
car :: ((t1 -> t2 -> t1) -> t) -> t
*Main> :t cdr
cdr :: ((t1 -> t2 -> t2) -> t) -> t
*Main> let test = cons 8 10
*Main> car test
8
*Main> cdr test
10

うまく動いてるようです

長方形を表現

SICPの問題2.3。長方形を表し、周囲の長さと面積を計算する手続きを作る。
今回は、問題2.2とほぼ同じなので、以下にソースと実行結果を示します。 

$cat rectangle.hs
data Point = Point Integer Integer

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

data Rectangle = Rectangle Point Point

instance Show Rectangle where
    show (Rectangle (Point x y) (Point x' y')) 
         = show (Point x y) ++ " - " ++ show (Point x' y)
                ++ "\n" ++ show (Point x y') 
                     ++ " - " ++ show (Point x' y')

makeRectangle:: Integer->Integer->Integer->Integer->Rectangle
makeRectangle x y x' y' = Rectangle (Point x y) (Point x' y')

getCircuit::Rectangle->Integer
getCircuit (Rectangle (Point x y) (Point x' y'))
        = double $( getLength x x') + (getLength y y')
        where
                double = (*) 2

getDimension (Rectangle (Point x y) (Point x' y'))
        = (*) (getLength  x x') (getLength  y y')

getLength::Integer->Integer->Integer
getLength x x' = abs ( x' -x )
$ghci rectangle.hs
Loading package base-1.0 ... linking ... done.
Compiling Main             ( rectangle.hs, interpreted )
Ok, modules loaded: Main.
*Main> makeRectangle 3 5 6 1
( 3 , 5 ) - ( 6 , 5 )
( 3 , 1 ) - ( 6 , 1 )
*Main> let rect = makeRectangle 3 5 6 1
*Main> getCircuit rect
14
*Main> getDimension rect
12

平面上の線分

今日は、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が式だったことを確認。すみませんでした。

使える文字

前回、関数の名前で定義できない文字を使いエラーを出していたので、名前に使える文字をまとめてみます。

まず、関数名。
1文字目は、アルファベットの小文字か、アンダースコア。
それ以降は、アルファベットの大文字、小文字。数字。アンダースコア。シングルクォート。
正規表現で書くとこんな感じかな?

 funcName = [a-z_][a-zA-Z0-9_']*

また、Haskellでは、アンダースコアから始まる関数名は、あまり良しとされていないようです。

次に、データコンストラクタ、型コンストラクタ、クラス名
これらは、関数名と違いアルファベットの大文字から始めなくてはなりません。また、クラス名は、データコンストラクタとは、かぶってはいけないようです。

話はかわりますが、マージンFXのひまわり証券さん、ニンテンドーDS Lite欲しい!と、いってもいいですか?

有理数の計算

前回の記事へ、id:nobusunさんからツッコミをいただいて、原因判明。Haskellの変数名は、小文字英数から始めないといけなかったんですね。で、id:nobusunさんが示していただいた、ソースに変更。

$ cat yuuri.hs
data Yuuri  = Yu Integer Integer

yAdd::Yuuri -> Yuuri -> Yuuri
yAdd (Yu s b ) (Yu s' b') =  Yu (s*b'+s'*b) (b*b')

変数が短く、シングルクォートも使っているところが、いかにもHaskellっぽいです。で、実行してみます。

$ ghci yuuri.hs

Loading package base-1.0 ... linking ... done.
Compiling Main             ( yuuri.hs, interpreted )
Ok, modules loaded: Main.
*Main> yAdd (Yu 2 3) (Yu 3 2)

Top level:
    No instance for (Show Yuuri)
      arising from use of `print' at Top level
    Probable fix: add an instance declaration for (Show Yuuri)
    In a 'do' expression: print it

ロードは、成功したけど、新たに問題が。エラーメッセージをみるとYuuriはShowのインスタンスでないから、表示させてあげないってことみたい。ということで、

$ cat yuuri.hs
data Yuuri  = Yu Integer Integer deriving Show

yAdd::Yuuri -> Yuuri -> Yuuri
yAdd (Yu s b ) (Yu s' b') =  Yu (s*b'+s'*b) (b*b')
$ ghci yuuri.hs

Loading package base-1.0 ... linking ... done.
Compiling Main             ( yuuri.hs, interpreted )
Ok, modules loaded: Main.
*Main> yAdd (Yu 2 3 ) (Yu 3 2)
Yu 13 6

dataが苦手

今日は、有理数の計算(足し算)をしようとして、

data Yuuri  = Yu Integer Integer

YAdd::Yuuri -> Yuuri -> Yuuri
YAdd (Yu s1 b1 ) (Yu s2 b2) =  Yu (s1*b2+s2*b1) (b1*b2)

というプログラムを書いて、ghciで実行しようとしたのですが、

$ ghci yuuri.hs

Loading package base-1.0 ... linking ... done.
Compiling Main             ( yuuri.hs, interpreted )

yuuri.hs:3:0: Not in scope: data constructor `YAdd'

yuuri.hs:4:0: Not in scope: data constructor `YAdd'
Failed, modules loaded: none.
Prelude>

うーん。