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