Haskellで単位付きの計算

Haskellの練習で、科学計算で使うような単位付きの計算を作ってみた。UnitNum型はNum, Show, Eqクラスのインスタンスで、==, +, -, *, show などが使える。

Gistはhttps://gist.github.com/2841750

-- Haskell unit calc test

data (Num a) => Unit a = Unit a a a -- dimensions of meter, kilogram, and second
  deriving (Eq)

instance (Num a)=>Show (Unit a) where
  show (Unit m k s) = (f "kg" k) ++ " " ++ (f "m" m) ++ " " ++(f "s" s)
    where
          f s 0 = ""
          f s 1 = s
          f s d = s ++ "^" ++ show d

instance (Num a,Num b)=>Show (UnitNum a b) where
  show (UnitNum a u) = show a ++ "[" ++ show u ++ "]"

data (Num a, Num b) => UnitNum a b = UnitNum a (Unit b)
  deriving (Eq)

instance (Num a,Num b) => Num (UnitNum a b) where
  (UnitNum a u1) + (UnitNum b u2)
      | u1 == u2 = UnitNum (a + b) u1
      | otherwise = error "Dimensions don't match."                            
  (UnitNum a u1) - (UnitNum b u2)
      | u1 == u2 = UnitNum (a - b) u1
      | otherwise = error "Dimensions don't match."                            
  (UnitNum a u1) * (UnitNum b u2) = UnitNum (a * b) (unitMult u1 u2)
  signum (UnitNum a u) = UnitNum (signum a) (Unit 0 0 0)
  abs (UnitNum a u) = UnitNum (abs a) u
  fromInteger x = UnitNum (fromIntegral x) (Unit 0 0 0)

unitNum :: (Num a,Num b)=> a -> (b,b,b) -> UnitNum a b
unitNum v (m,k,s) = UnitNum v (Unit m k s)

unitMult :: Num a => Unit a -> Unit a -> Unit a
unitMult (Unit m1 k1 s1) (Unit m2 k2 s2) = Unit (m1+m2) (k1+k2) (s1+s2)

unitDiv :: Num a => Unit a -> Unit a -> Unit a
unitDiv (Unit m1 k1 s1) (Unit m2 k2 s2) = Unit (m1-m2) (k1-k2) (s1-s2)

やたらtype constraintsをつける場所が多くなった。まだこのあたりあまりよく分かっておらず、何回もコンパイルエラーで=>での指定が足りないと言われ、しばらく修正してやっとコンパイルが通った。

使ってみる。

Prelude> :l unitcalc.hs 
[1 of 1] Compiling Main             ( unitcalc.hs, interpreted )
Ok, modules loaded: Main.
*Main> let a = unitNum 1 (0,1,0)
*Main> let b = unitNum 2 (1,0,-2)
*Main> a
1[kg  ]
*Main> b
2[ m s^-2]
*Main> a * b
2[kg m s^-2]
*Main> a + b
*** Exception: Dimensions don't match.

良い感じだ。

割り算(/)を使うにはFractionalクラスのインスタンスにしないといけないが、Fractionalの使い方(というか、数値クラスの構造)をまだよく分かっていないので未実装。

それと、本当は次元が違う量の加減演算をコンパイル時にチェックしてはじいたり出来たらクールなんだろうけど。数学の行列の演算で次元をコンパイル時にチェックしようというのと同じような話か。そんな話は聞いたことがないので、おそらく不可能だろう。たかだか数次元までの整数次元だけ対応するなら異なる次元ごとに違う型を用意すれば良いのかもしれないけど、それは現実的な解とは言いがたい。(よく知らないが、でもそういう研究はどこかでされていそう。)