haskellの数値のなんかを自分なりに考えてみた。
思いついたやり方は2つ。
Doubleが最も上の階層にあることを利用する方法
IntegerもRationalもDoubleにできるのでこれを利用する。足し算を実行したい型が全てDoubleより下の階層にあることがわかっている場合はこんなんでもよいと思う。
fooDouble :: Foo -> Double
fooDouble (FooInteger x) = fromInteger x
fooDouble (FooDouble x) = x
fooDouble (FooRational x) = fromRat x
fooPlus :: Foo -> Foo -> Double -> Foo
fooPlus (FooDouble _) _ = FooDouble
fooPlus _ (FooDouble _) = FooDouble
fooPlus (FooRational _) _ = FooRational . toRational
fooPlus (FooInteger _) (FooInteger _) = FooInteger . truncate
fooPlus y x = fooPlus x y
addFoo' :: Foo -> Foo -> Foo
addFoo' x y = fooPlus x y $ fooDouble x + fooDouble y
それ以外
特定の型同士を相互変換して足し算を実行する。
この方法だとinstance宣言のときにかなり自由度の高い型のパターンマッチが可能。さらに足し算できない型同士を足そうとしたらコンパイルエラーまで出てくれる。その代わりOverlappingの制限が厳しいので書き下さないといけないケースが多くてつらい。
Type Familyは足し算を実行したあとの結果の型を強制するのに使う。これがないといちいちaddBar x y :: Double
などと書かなければいけないのでとてもめんどくさい。
{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances, TypeFamilies, UndecidableInstances #-}
data Bar a = Bar a deriving (Show)
class BarLaw a b where
barPlus :: a -> b -> Result a b
type family Result a b :: *
type instance Result Double a = Double
type instance Result a Double = Double
type instance Result Rational Integer = Rational
type instance Result Rational Rational = Rational
type instance Result Integer Rational = Rational
type instance Result Integer Integer = Integer
instance BarLaw Integer Double where
barPlus x y = fromIntegral x + y
instance BarLaw Double Integer where
barPlus x y = x + fromIntegral y
instance BarLaw Rational Integer where
barPlus x y = x + fromIntegral y
instance BarLaw Integer Rational where
barPlus x y = fromIntegral x + y
instance BarLaw Rational Double where
barPlus x y = fromRat x + y
instance BarLaw Double Rational where
barPlus x y = x + fromRat y
instance (a ~ Result a a, Num a) => BarLaw a a where
barPlus x y = x + y
addBar :: (BarLaw a b) => Bar a -> Bar b -> Bar (Result a b)
addBar (Bar x) (Bar y) = Bar $ x `barPlus` y
パターンマッチに柔軟性を持たせるか、型に柔軟性を持たせるか、お好きな方で。