# 108: Ratio クラスの作成 [↑up](bunny_notes) - issued: 2020-06-10 - 分類: B 機能追加 - status: Open ## 概要 まだ Prelude.hs 以外の import 機能がないため、testcases/myratio.hs 上で Ratio クラスのインプリを進める。 いろいろ未実装機能がでてくると思われる。 ## 調査ログ ## 2020-06-10 現在の 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 だけでいいということも ありうるのでは…。ないか、保留しておいて、あとで考えよう(まずは単純に作ろう)。 ## 2020-06-12 単純なところから。${data (Integral a) => a :% a} と書けるようにしよう。 - 右辺に infix operator の形を許す - context $ {(Integral a) =>} をうけつける まず、右辺に infix operator を許すようにした。後者はまだ。 次は後者。$ {'a' :% 'b'} が現状ではコンパイルエラーにならないのだが、 これが弾かれるようにすべき。 ## 2020-06-21 どうも、調子がでないので、ちいさなステップにわけて乗り越えよう。 instance (Integral a) => Ord (Ratio a) を対応するのに、 - DictPass において、決め打ちで Ratio に対しては Integral とするような改造をいれる - 決め打ちじゃなくて、instance 宣言で収集した情報をもとに、上と同じ動作を実現する - Integral の代わりに (Num a, Ord 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 でコミットしておく。 ## 2020-08-12 いいかげんでもいいから実装をすすめよう、といいつつ、あまりにいい加減すぎるのもあれなので、少し調べる。 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 $$} ## 2020-08-14 つぎ、つまづくところまで進めよう。…と思ったが、Real や Fractional をこのまま(Prelude.hs にインポートできないまま)つづけても面白くはなさそう。 Prelude 側で Fractional とかつくってみるかな。