# 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 とかつくってみるかな。