まだ Prelude.hs 以外の import 機能がないため、testcases/myratio.hs 上で Ratio クラスのインプリを進める。
いろいろ未実装機能がでてくると思われる。
現在の myratio.hs は以下のとおり:
infixl 7 %
ratPrec = 7 :: Int
-- todo: (Integral a) =>
-- :%, infix constructor
-- deriving Eq
data Ratio a = Rat a a
type Rational = Ratio Integer
instance (Eq a) => Eq (Ratio a) where
Rat a b == Rat c d = a == c && b == d
reduce _ 0 = error "Ratio.% : zero denominator"
reduce x y = Rat (x `quot` d) (y `quot` d)
where d = gcd x y
x % y = reduce (x * signum y) (abs y)
numerator (Rat x _) = x
denominator (Rat _ y) = y
instance (Integral a) => Ord (Ratio a) where
Rat x y <= Rat x' y' = x * y' <= x' * y
Rat x y < Rat x' y' = x * y' < x' * y
instance (Integral a) => Num (Ratio a) where
Rat x y + Rat x' y' = reduce (x*y' + x'*y) (y*y')
Rat x y * Rat x' y' = reduce (x*x') (y*y')
negate (Rat x y) = Rat (-x) y
abs (Rat x y) = Rat (abs x) y
signum (Rat x y) = Rat (signum x) 1
fromInteger x = Rat (fromInteger x) 1
-- todo: Integral is a instance of Show?
-- showParen
instance (Show a) => Show (Ratio a) where
show (Rat x y) = show x ++ " % " ++ show y
main = do print a
print b
print (a < b)
-- print $ a + b
-- print $ a * b
-- print (- a)
-- print (fromInteger 8 :: Ratio Int)
where a, b :: (Ratio Integer)
a = 5 % 10
b = 1 % 2
コメントにも書いてあるような work-aroud をほどこしつつ仮実装をすすめているが、 上記のコードでランタイムエラーが発生する。
原因は、CompositDict の作成に不具合があるため。 print (a < b) のところで、< に渡す辞書を生成しているのだが、 以下のように、(CompositDict ${Main.Ratio Prelude.Ord [${Prelude.Integer Prelude.Ord}])} を渡している。
((((Prelude.< :: ([Prelude.Ord t21] :=> (t21 -> (t21 -> Prelude.Bool))))
(CompositDict ${Main.Ratio Prelude.Ord} [${Prelude.Integer Prelude.Ord}]))
(Main.l27.l0.a :: (Main.Ratio Prelude.Integer)))
(Main.l27.l0.b :: (Main.Ratio Prelude.Integer))))))
だが、instance (Integer a) => Ord (Ratio a) ... であるから、 CompositDict の引数は ${Prelude.Integer Prelude.Ord } ではなく、${Prelude.Integer Prelude.Integral } でなくてはならない。 CompositDict 全体が Ord クラスの辞書だからといって、その引数も Ord とは限らず、インスタンス宣言によって適切な辞書を選ぶ必要があった。
DictPass の以下の部分(findApplyDict の一部)で、(ty2dict n2) の n2 の部分が、 ここでいう Ord にあたるのだが、これが n2 とイコールとは限らず、instance n2 n1 宣言の context によって適切なものを指定しなければならない。
ty2dict n2 ty@(TAp _ _) = do
let (n1, ts) = extr' ty []
cdd = Var (DictVar n1 n2)
-- todo: the order of cdds shold be reordered
cdds <- mapM (ty2dict n2) ts
return $ Var (CompositDict cdd cdds)
where extr' (TCon (Tycon n1 _)) ts = (n1, ts)
extr' (TAp t1 t2) ts = extr' t1 (t2:ts)
ってことは、辞書定義情報を DictPass にひきわたしておかないといけないわけだが、 現状どうだったかな。
なさそう↓
data TcState = TcState { tcCe :: !ClassEnv
, tcPss :: ![(Pred, Id)]
, tcSubst :: !Subst
, tcNum :: !Int
, tcIntegerTVars :: ![Type]
}
deriving Show
instance 宣言の context をそのままというわけにはいかないはずなので、 すこし考える必要がある。たとえば、instance (Ord a, Num a) => Ord (Hoge a) とかで、 あるメソッドは Num がいるが、ほかのメソッドでは Ord だけでいいということも ありうるのでは…。ないか、保留しておいて、あとで考えよう(まずは単純に作ろう)。
単純なところから。data (Integral a) => a :% a と書けるようにしよう。
まず、右辺に infix operator を許すようにした。後者はまだ。
次は後者。'a' :% 'b' が現状ではコンパイルエラーにならないのだが、 これが弾かれるようにすべき。
どうも、調子がでないので、ちいさなステップにわけて乗り越えよう。
instance (Integral a) => Ord (Ratio a) を対応するのに、
最初のステップ、決め打ちで Ord (Ratio a) の a への制約を Integral にする:
--- a/compiler/src/DictPass.hs
+++ b/compiler/src/DictPass.hs
@@ -231,7 +231,10 @@ findApplyDict e (qv :=> t') (_ :=> t) = do
ty2dict n2 (TAp (TCon (Tycon n1 _)) ty) = do
let cdd = Var (DictVar n1 n2)
- cdds <- mapM (ty2dict n2) [ty]
+ n3 | n1 == "Main.Ratio" && n2 == "Prelude.Ord" = "Prelude.Integral"
+ | otherwise = n2
+ trace (show (n1, n2, n3)) $ return ()
+ cdds <- mapM (ty2dict n3) [ty]
return $ Var (CompositDict cdd cdds)
ty2dict n2 ty@(TAp _ _) = do
ここで n3 は (n1, n2) の関数であることに注意。 instance (Integral a) => Ord (Ratio a) の Ratio, Ord, Integral がそれぞれ n1, n2, n3 に対応する。
つぎは、この情報を Rename で獲得し、DictPass に受け渡すところの実装。
ひとまず、branch : instanceContext108 でコミットしておく。
いいかげんでもいいから実装をすすめよう、といいつつ、あまりにいい加減すぎるのもあれなので、少し調べる。
instance 宣言の例として、「すごいH~」、「Haskell 入門」にでてくるものは、いずれも単純なもの(コンテキストのない instance Eq Hoge where.. 様のもの)のみ。
Standard Prelude には、以下の形が出現する:
instance (Show a) => Show [a] where ... instance (Show a, Show b) => Show (a,b) where ...
これしかでてこなかったので、複合辞書のクラスを決め打ちできた(Show [a] から Show a を勝手に推定していた)のが、Ratio で崩れた格好。
Control.Monad には以下の形が出現する:
instance Ix i => Functor (Array i)
Data.Array にも、結構複雑な形がでてくるなぁ。
まぁ、ひとまず Ratio の例のみカバーできる実装でお茶を濁しつつ先にすすむか(いま不調だし。まじめにやるのは力が満ちているときに)。
割り切って、( "Main.Ratio", "Prelude.Ord") \(\mapsto\) "Prelude.Integral" となるような辞書をつくって使う。
件の情報は、ひとまず [((Id, Id), Id)] 型にする。instance context ということで、 IContext という名前で。
Rename でこの情報を収集、DictPass でこれを使うように変更:
findApplyDict e (qv :=> t') (_ :=> t) = do
unify' t' t
@@ -230,10 +231,11 @@ findApplyDict e (qv :=> t') (_ :=> t) = do
mkdicts qs (d:ds)
ty2dict n2 (TAp (TCon (Tycon n1 _)) ty) = do
+ iconst <- tcIContext <$> get
let cdd = Var (DictVar n1 n2)
- n3 | n1 == "Main.Ratio" && n2 == "Prelude.Ord" = "Prelude.Integral"
- | otherwise = n2
- trace (show (n1, n2, n3)) $ return ()
+ n3 = case lookup (n1, n2) iconst of
+ Just s -> s
+ Nothing -> n2
cdds <- mapM (ty2dict n3) [ty]
return $ Var (CompositDict cdd cdds)
やっつけ実装ではあるが、test をクリアした状態のまま半歩すすめたのでよしとする。
master ブランチにマージすることにする。
Num の Instance の方もうごくようになったので、myratio でコメントアウトしてあったのをはずす(ついでに、値も少し変更):
main = do print a
print b
-- print c
print (a < b)
print $ a + b
print $ a * b
print (- a)
print (fromInteger 8 :: Ratio Int)
where a, b :: (Ratio Integer)
a = 2 % 6
b = 768 % (768 * 2)
-- c = 'a' :% 'c' -- should be an error
実行結果:
1 % 3 1 % 2 True 5 % 6 1 % 6 -1 % 3 8 % 1
つぎ、つまづくところまで進めよう。…と思ったが、Real や Fractional をこのまま(Prelude.hs にインポートできないまま)つづけても面白くはなさそう。
Prelude 側で Fractional とかつくってみるかな。