108: Ratio クラスの作成

↑up

概要

まだ Prelude.hs 以外の import 機能がないため、testcases/myratio.hs 上で Ratio クラスのインプリを進める。

いろいろ未実装機能がでてくると思われる。

調査ログ

2020-06-10 (Wed)

現在の 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 (Fri)

単純なところから。data (Integral a) => a :% a と書けるようにしよう。

まず、右辺に infix operator を許すようにした。後者はまだ。

次は後者。'a' :% 'b' が現状ではコンパイルエラーにならないのだが、 これが弾かれるようにすべき。

2020-06-21 (Sun)

どうも、調子がでないので、ちいさなステップにわけて乗り越えよう。

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 でコミットしておく。

2020-08-12 (Wed)

いいかげんでもいいから実装をすすめよう、といいつつ、あまりにいい加減すぎるのもあれなので、少し調べる。

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 (Fri)

つぎ、つまづくところまで進めよう。…と思ったが、Real や Fractional をこのまま(Prelude.hs にインポートできないまま)つづけても面白くはなさそう。

Prelude 側で Fractional とかつくってみるかな。