Haskellは素晴らしい

何故か、Vertigoのソースを眺めていたら(読めない)、IOExtsをimportしていたので、調べて見たら、unsafePerformIO :: IO a -> aとかいう関数が。一旦、モナド界に行ったら、現世には帰ってこれないはずなんじゃ・・・。Maybeモナドにも、fromJustとかあるし。むー

import System.IO.Unsafe
main=
 let c = (unsafePerformIO getChar)
  in if (c=='a') then (putChar 'A') else (putChar c)


多項式計算。多項式の正規化(normalize)が全て。もっと簡単に書けると思って始めたら、すごい長くなった。もっと賢いやり方があるはず

module Polynomial where

data Polynomial a = 
 Const a | Var String |PLUS (Polynomial a) (Polynomial a)|MULT (Polynomial a) (Polynomial a)


instance Show a => Show (Polynomial a) where
  show (Const n) = show n
  show (Var str) = show str
  show (PLUS x y) = "(+ " ++ " " ++ (show x) ++ " " ++ (show y) ++ ")"
  show (MULT x y) = "(* " ++ " " ++ (show x) ++ " " ++ (show y) ++ ")"

instance Num a => Eq (Polynomial a) where
   p == q = (term_eq (normalize p) (normalize q))

instance Num a => Num (Polynomial a) where
   p+q = (normalize (PLUS p q))
   p*q = (normalize (MULT p q))
   p-q = (normalize (PLUS p (MULT (Const (negate 1)) q)))
   fromInteger n = (Const (fromInteger n))

--項としての比較。x+4と4+xは多項式としては等しいが、項としては異なる
--多項式としての比較は、Eqのinstanceとして
term_eq :: (Eq a) => (Polynomial a) -> (Polynomial a) -> Bool
term_eq p q = case (p,q) of
  ((Const x) , (Const y))   ->  (x == y)
  ((Var x)  , (Var y)  )   -> (x == y)
  ((PLUS x1 x2) , (PLUS y1 y2))  ->  (term_eq x1 y1) && (term_eq x2 y2)
  ((MULT x1 x2) , (MULT y1 y2))  ->  (term_eq x1 y1) && (term_eq x2 y2)
  ( _  , _) -> False


--単項式の和に展開
expand :: (Num a) => (Polynomial a) -> (Polynomial a)
expand p = 
 let next = (expand1 p)
  in if (term_eq p next) then next else (expand next)
 where
  expand1 p = case p of
    (Const n) -> (Const n)
    (Var v) -> (Var v)
    (PLUS x y) ->  (PLUS (expand1 x) (expand1 y))
    (MULT x (PLUS y z)) -> (expand1 (PLUS (MULT x y) (MULT x z)))
    (MULT (PLUS x y) z) -> (expand1 (PLUS (MULT x z) (MULT y z)))
    (MULT x y) -> (MULT (expand1 x) (expand1 y))
  
--単項式を正規化(引数は単項式と仮定されている。頭悪い)
----単項式をリスト化(e.g. x*3*y->["x" , 3 ,"y"]
flatten_monomial :: (Num a) => (Polynomial a) -> [(Polynomial a)]
flatten_monomial p = case p of
  (Const n) -> [(Const n)]
  (Var x) -> [(Var x)]
  (MULT x y) -> (flatten_monomial x) ++ (flatten_monomial y)

----単項式p,qの比較(p<q)。正規化してあること前提。sort_monomialでも使う
gt_monomial :: (Polynomial a) -> (Polynomial a) -> Bool
gt_monomial p q = case (p,q) of
  ((Const n) , (Const m)) -> False
  ((Const n) , p) -> False
  (p , (Const n)) -> True
  ((Var x) , (Var y)) -> (x < y)
  ((Var x) , (MULT (Var z) w)) -> (x > z)
  ((MULT (Var z) w) , (Var x)) -> (not (x < z))
  ((MULT (Var x) y) , (MULT (Var z) w)) -> (if (x == z) then (gt_monomial y w) else (x < z))
  ((MULT (Const n) x) , y) -> (gt_monomial x y)
  (x , (MULT (Const n) y)) -> (gt_monomial x y)

----リスト化した単項式をソート
sort_monomial :: (Num a) => [(Polynomial a)] -> [(Polynomial a)]
sort_monomial [] = []
sort_monomial (x:xs) 
  = (sort_monomial [y|y<-xs , (gt_monomial x y)]) 
     ++ [x] ++ (sort_monomial [y|y<-xs , (not (gt_monomial x y))])

----平坦化した単項式を戻す
list_to_monomial :: (Num a) => [(Polynomial a)] -> (Polynomial a)
list_to_monomial p = case p of
  [x] -> x
  ((Const 0):xs) -> (Const 0)
  ((Const 1):xs) -> (list_to_monomial (reverse xs))
  ((Const n):xs) -> (MULT (Const n) (list_to_monomial (reverse xs)))
  (x:xs) -> (MULT x (list_to_monomial xs))

----定数部分を計算してしまう
reduce_monomial :: (Num a) => [(Polynomial a)] -> [(Polynomial a)]
reduce_monomial v = case v of
   ((Const n):((Const m):xs)) -> (reduce_monomial ((Const (n*m)) : xs))
   ((Var x):xs) -> ((Const 1):((Var x):xs))
   x ->x

----Normalizing Monomial
normalize_monomial :: (Num a) => (Polynomial a) -> (Polynomial a)
normalize_monomial p 
       = list_to_monomial (reduce_monomial (sort_monomial (flatten_monomial p)))


--多項式の正規化(頭悪い)
----多項式を(正規化した)単項式のリストに分解。expandしてあること前提
flatten_polynomial :: (Num a) => (Polynomial a) -> [(Polynomial a)]
flatten_polynomial p = case p of
  (PLUS x y) -> (flatten_polynomial x) ++ (flatten_polynomial y)
  x -> [(normalize_monomial x)]

----単項式の足し算
add_monomial :: (Num a) => (Polynomial a) -> (Polynomial a) -> (Polynomial a)
add_monomial p q = case (p,q) of
  ((Const n) , (Const m)) -> (Const (n+m))
  ((MULT (Const n) x) , (MULT (Const m) y)) 
    -> (if (eq_monomial x y)
	then (MULT (Const (n+m)) x)
	else (PLUS (MULT (Const n) x)  (MULT (Const m) y)))
  (x , (MULT (Const n) y))
    -> (if (eq_monomial x y) 
        then (MULT (Const (n+1)) x)
        else (PLUS x (MULT (Const n) y)))
  ((MULT (Const n) x) , y)
    -> (if (eq_monomial x y) 
        then (MULT (Const (n+1)) x)
        else (PLUS (MULT (Const n) x) y))
  (x , y) -> (PLUS x y)
 where
   eq_monomial x y = (not (gt_monomial x y)) && (not (gt_monomial y x))

----単項式リストを多項式に戻す
list_to_polynomial :: (Num a) => [(Polynomial a)] -> (Polynomial a)
list_to_polynomial p = case p of
   [x] -> x
   ((Const 0):xs) -> (list_to_polynomial xs)
   ((Const n):((Const m):xs)) -> list_to_monomial ( (Const (n+m)) : xs )
   (x:[y]) -> (add_monomial x y)
   (x:(y:xs)) 
    -> (if (eq_monomial x y) 
        then (PLUS (add_monomial x y) (list_to_polynomial xs))
        else (PLUS x (PLUS y (list_to_polynomial xs))))
 where
   eq_monomial x y = (not (gt_monomial x y)) && (not (gt_monomial y x))

----Normalizing polynomial
normalize :: (Num a) => (Polynomial a) -> (Polynomial a)
normalize p = list_to_polynomial (reverse (sort_monomial (flatten_polynomial (expand p))))

これだけだと使いづらいので、パーサ

import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Char
import Text.ParserCombinators.Parsec.Token
import Text.ParserCombinators.Parsec.Language
import Text.ParserCombinators.Parsec.Expr
import Monad
import Maybe
import Polynomial 


--字句解析
lexer  = makeTokenParser (haskellDef {reservedOpNames = ["+","*","-"]})
kwd   = reserved lexer
ident = identifier lexer

--構文解析
leftA s f = Infix (do { reservedOp lexer s; return f }) AssocLeft
table     = [[leftA "*" MULT] , [leftA "+" PLUS]]

polynomial_expr = buildExpressionParser table term
term = variable <|> constant <|> parens lexer polynomial_expr
constant    = liftM Const (natural lexer)
variable    = liftM Var ident

run :: Show a => Parser a -> String -> Maybe a
run p input = case (parse p "" input) of
              Left err -> Nothing
              Right x  -> Just x

pparse :: String -> Polynomial Integer
pparse str = fromJust (run polynomial_expr str)

Parsecはよく分からないし、なんでこれでいいのかもよく分かってない。とりあえず、動く

テスト。出力がS式なのは手抜き

*Main> (pparse "x*3 + y*(y+x*x+z)") + (pparse "x*x")
(+  (*  "x" (*  "x" "y")) (+  (*  "x" "x") (+  (*  3 "x") (+  (*  "y" "y") (*  "y" "z")))))

本当は、グレブナー基底の計算まで実装しようと思ってたけど、予想外に時間くったので、ここで終了。まあ、あと剰余計算書いたら、実質終わり


感想。なんというか、ひたすらパターンマッチだし、なんかS式書いてるのと、あんまり変わらないなー。別の言語というよりは、better Schemeな感じだ。

じゃあSchemeでこれを書いてたらどうなってたかというと、途中でめんどくさくなってたんじゃないかと。使用暦数年のScheme < 使用暦数日のHaskellC/C++を使っていて、型があって助かると感じたことはあまりないけど、Haskellのは頭を整理する役に立つ。強い型付けも悪くないと思った。アセンブラをごりごりに書いたことがある人なら、Cの型でも便利と思ったりするんだろうか。実質上、Inductiveなデータ型というのは、S式とほとんど変わらないし、S式はInductiveなデータ構造の中で最も汎用的には違いないけど、Haskellみたいに型チェックしてくれる方が楽。

もっとも、数年前だったら挫折してたと思う。数年前は、Haskellについて書かれたサイトなんて数えるほどしかなかったし。今は、適当に検索すると、一杯ひっかかって、サンプルコードを適当に眺めれば大体なんとなく分かってくるので、実はそれが一番重要かもしれない

けど、最近、パターンマッチで済むことばっかやってるので、頭が悪くなるんじゃないかと心配。