Hatena::ブログ(Diary)

あどけない話

2009-02-04

Haskell のデータ構築子

Haskell代数データ型で使われるデータ構築子は、実は関数と同様に扱えます。たとえば、四則演算の式を表す代数データ型を以下のように定義したとします。

data Expr = C Int | Add Expr Expr | Sub Expr Expr
          | Mul Expr Expr | Div Expr Expr deriving Show

"1 + 2" は "Add (C 1) (C 2)" と表現できます。この式を評価してみましょう。

Add (C 1) (C 2) → Add (C 1) (C 2)

当たり前ですが、そのものが返ってきます。

話は変わりますが、add という関数を素直に定義すると、こうなるでしょう。

add x y = x + y

式 "add 1 2" を評価するとこうなります。

add 1 23

すなわち、関数であれば簡略化されますが、データ構築子であれば簡略化されないところだけが違います。

全置表現(あるいは Lisp もどき)

Expr を実際に計算するプログラムを書きましょう。

data Expr = C Int | Add Expr Expr | Sub Expr Expr
          | Mul Expr Expr | Div Expr Expr deriving Show

evaluate :: Expr -> Int

evaluate (C x) = x
evaluate (Add e1 e2) = evaluate e1 + evaluate e2
evaluate (Sub e1 e2) = evaluate e1 - evaluate e2
evaluate (Mul e1 e2) = evaluate e1 * evaluate e2
evaluate (Div e1 e2) = evaluate e1 `div` evaluate e2

(10 + 8 / 2 * 7 - 4) に相当する Expr を evaluate の引数に指定すると、こうなります。

evaluate (Sub (Add (C 10) (Mul (Div (C 8) (C 2)) (C 7))) (C 4)) → 34

二項演算子1

データ構築子は関数のように扱えるので、バッククートで囲むと二項演算子になります。

data Expr = C Int | Expr `Add` Expr | Expr `Sub` Expr
          | Expr `Mul` Expr | Expr `Div` Expr deriving Show

evaluate :: Expr -> Int

evaluate (C x) = x
evaluate (e1 `Add` e2) = evaluate e1 + evaluate e2
evaluate (e1 `Sub` e2) = evaluate e1 - evaluate e2
evaluate (e1 `Mul` e2) = evaluate e1 * evaluate e2
evaluate (e1 `Div` e2) = evaluate e1 `div` evaluate e2

こうすると、二項演算子関数適用より弱いので括弧が減って、Expr がずいぶん読みやすくなります。

evaluate ((C 10 `Add` ((C 8 `Div` C 2) `Mul` C 7)) `Sub` C 4) → 34

二項演算子2

Haskell なんですから、本当の二項演算子を使いましょう。data の中では二項演算子の名前は ":" から始める必要があるそうです。

data Expr = C Int | Expr :+ Expr | Expr :- Expr
          | Expr :* Expr | Expr :/ Expr deriving Show

evaluate :: Expr -> Int

evaluate (C x) = x
evaluate (e1 :+ e2) = evaluate e1 + evaluate e2
evaluate (e1 :- e2) = evaluate e1 - evaluate e2
evaluate (e1 :* e2) = evaluate e1 * evaluate e2
evaluate (e1 :/ e2) = evaluate e1 `div` evaluate e2

記号になるだけで、ずいぶん直感的になりますね。

evaluate ((C 10 :+ ((C 8 :/ C 2) :* C 7)) :- C 4) → 34

優先順位

二項演算子には、優先順位が付けられるはずです。という訳で、これが最終的なプログラムです。

data Expr = C Int | Expr :+ Expr | Expr :- Expr
          | Expr :* Expr | Expr :/ Expr deriving Show

evaluate :: Expr -> Int

evaluate (C x) = x
evaluate (e1 :+ e2) = evaluate e1 + evaluate e2
evaluate (e1 :- e2) = evaluate e1 - evaluate e2
evaluate (e1 :* e2) = evaluate e1 * evaluate e2
evaluate (e1 :/ e2) = evaluate e1 `div` evaluate e2

infixl 6 :* , :/
infixl 5 :+ , :-

以下の実行例を見れば、感動を押さえきれないでしょう。。。

evaluate (C 10 :+ C 8 :/ C 2 :* C 7 :- C 4) → 34

おまけ

あなたの言語で、これできますか?

kuroishikuroishi 2009/02/05 09:37 なんかエントリーと関係ないかもですが...たまたま perl 本で、 operators の部分を読んでいて面白い部分があったので。

From a mathematical perspective, operators are just ordinary functions with special syntax. From a linguistic perspective, operators are just irregular verbs. But as any linguist will tell you, the irregular verbs in a language tend to be the ones you use most often. And that's important from an information theory perspective because the irregular verbs tend to be shorter and more efficient in both production and recognition.

larry wall は、 perl は自然言語のような雰囲気を出したいみたいな話を聞いたことがあるのですが、一つ目のパラメータが主語で、演算子が動詞、2つ目のパラメータを目的語として捉えてるのかと思ったのでした。

[1..100]>>=pen[1..100]>>=pen 2011/07/02 00:33 ruby だとこうやるのかな。
class C
def v; @v; end
def initialize(v); @v = v; end
def +(other); C.new(@v + other.v); end
def -(other); C.new(@v - other.v); end
def *(other); C.new(@v * other.v); end
def /(other); C.new(@v / other.v); end
def inspect; "C[#{@v.inspect}]"; end
def self.[](x); new(x); end
end
p(C[10] + C[8] / C[2] * C[7] - C[4]) => C[34]

itto100penitto100pen 2011/07/02 01:04 こちらの方が意図に合ってますか。
メタプラグラミングすればもっと短くなると思いますがなれてなくて。
class Expr
def v; @v; end
def initialize(v); @v = v; end
def +(other); Add.new([self,other]); end
def -(other); Sub.new([self,other]); end
def *(other); Mult.new([self,other]); end
def /(other); Div.new([self,other]); end
end
class C < Expr
def self.[](x); new(x); end
def evaluate; @v; end
end
class Add < Expr
def evaluate; x,y = @v; x.evaluate + y.evaluate; end
end
class Sub < Expr
def evaluate; x,y = @v; x.evaluate - y.evaluate; end
end
class Mult < Expr
def evaluate; x,y = @v; x.evaluate * y.evaluate; end
end
class Div < Expr
def evaluate; x,y = @v; x.evaluate / y.evaluate; end
end
p((C[10] + C[8] / C[2] * C[7] - C[4]).evaluate) => 34

kazu-yamamotokazu-yamamoto 2011/07/04 10:01 この Ruby のコードって、Haskell のコードと比べて嬉しいんですか?

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/kazu-yamamoto/20090204/1233732939