# 045: DictPass における defaulting がその場しのぎ [↑up](bunny_notes) - issued: 2020-04-22 - 分類: 分類:C 改善項目 - status: Open ## 概要 DictPass における defaulting がその場しのぎの実装になっている。 ## 調査ログ ### 2020-04-22 [044](bissue044) 対処のなかで、DictPass 中で defaulting 相当のことを実施する必要が生じて、 その場しのぎの実装を行った。 Type Check を通してはいけないケースを通してしまうようなダメさはないのだが、 実装としてもいいかげんだし、特殊なケースにしか対応していないので、あとで改善が必要。 以下に、おこなった内容を記録しておく。 まず、044 の対処として、TrCore.trExpr2 が整数リテラルにつける型を、Integer から Num a => a に変更 そうすることで、DictPass における findApplyDict が失敗するようになった。 この変更前は、Integer を探し当てていたケースが、総称型にいきあたってしまうため。 そこで、DictPass の TcState に、このような Num a => a になっている整数リテラルに であったら、それを記録するリストを追加: $$
{
-data TcState = TcState { tcCe :: ClassEnv
- , tcPss :: [(Pred, Id)]
- , tcSubst :: Subst
- , tcNum :: Int
+data TcState = TcState { tcCe :: ClassEnv
+ , tcPss :: [(Pred, Id)]
+ , tcSubst :: Subst
+ , tcNum :: Int
+ , tcIntegerTVars :: [Type]
}
$$}
つぎに、DictPass.getTy にて、整数リテラルに出会った際に、この追加したリストに記録していく。
$${
+getTy e@(Lit (LitInt _ qt@(_ :=> v))) = do
+ st <- get
+ let tvars = tcIntegerTVars st
+ st' = st{tcIntegerTVars = (v:tvars)}
+ put st'
+ return qt
$$}
最後に、findApplyDict において、このリストに記録されている型変数と同一化されるような
型変数については、Integer 型の辞書を生成するようにした。
$${
findApplyDict e (qv :=> t') (_ :=> t) = do
unify' t' t
s <- getSubst
+ itvars <- tcIntegerTVars <$> get
+ let itvars' = fmap (apply s) itvars
let mkdicts [] ds = return ds
mkdicts (IsIn n2 (TVar x) : qs) ds =
case apply s (TVar x) of
(TCon (Tycon n1 _)) -> mkdicts qs (Var (DictVar n1 n2) : ds)
- y -> do v <- lookupDictArg (n2, x)
- case v of
- Nothing -> error ("Error: dictionary not found: "
- ++ n ++ ", " ++ show (x,n2,y))
- Just v' -> mkdicts qs (Var v' : ds)
+ y | y `elem` itvars'
+ -> mkdicts qs (Var (DictVar "Prelude.Integer" n2) : ds)
+ | otherwise
+ -> do v <- lookupDictArg (n2, x)
+ case v of
+ Nothing -> error ("Error: dictionary not found: "
+ ++ n ++ ", " ++ show (x,n2,y,itvars))
+ Just v' -> mkdicts qs (Var v' : ds)
mkdicts _ _ = error "mkdicts: must not occur"
dicts <- mkdicts qv []
return (foldl App e dicts)
$$}
これで、整数リテラルが曖昧なケースに "Integer" に defaulting するケースに限っては、
一応正しく動くのではないかと思われる。
### 2020-04-28
うえで「一応正しく動くのではないか」と書いたのは、誤り。
一見うごいているように見えるが、このやり方では正しくない (実装としてその場しのぎであるだけでなく、間違っている)。
それが明らかな例が g2.hs:
$${
$ cat testcases/g2.hs
g x = x + 1
a :: Int
a = 9
main = putStrLn $ show $ g a
$$}
これの core は次のようになっている:
$${
---- ddumpCore ----
(Main.main :: (Prelude.IO ())) =
((Prim.putStrLn :: ([Prelude.Char] -> (Prelude.IO ()))) $
(((Prelude.show :: ([Prelude.Show t4] :=> (t4 -> [Prelude.Char])))
${Prelude.Int Prelude.Show}) $
(((Main.g :: ([Prelude.Num t5] :=> (t5 -> t5)))
${Prelude.Int Prelude.Num})
(Main.a :: Prelude.Int))))
(Main.g :: ([Prelude.Num t9] :=> (t9 -> t9))) =
\(Main.g.DARG0 :: Ä) ->
\(_Main.g.U1 :: ([Prelude.Num t6] :=> t6)) ->
((((Prelude.+ :: ([Prelude.Num t7] :=> (t7 -> (t7 -> t7))))
${Prelude.Integer Prelude.Num})
(_Main.g.U1 :: ([Prelude.Num t6] :=> t6)))
(1 :: ([Prelude.Num t8] :=> t8)))
(Main.a :: Prelude.Int) =
(9 :: ([Prelude.Num t9] :=> t9))
$$}
Main.g の型は ${Num a => a -> a} なので、この関数は Num クラスの辞書をうけとる。また、呼び出し側(Main.main)では、 a が Int なので、Int の辞書を渡している。そこまではあっている。
だが、現状では Main.g において、 x + 1 の 1 に誤った defaulting を施してしまい、
うけっとった辞書ではなく Integer の辞書を (+) にわたしてしまっている。
これでも動いているように見えるのは、現在のランタイムにおいては、Int と
Integer が実質的に同じであるせい。
defaulting の規則がきめうち (Num a => a を Integer にする)なのはいいとしても、
現状の制約されていない整数リテラルにであったら Integer にしてしまうのはまずいので、
これを正す必要がある。
これを直さないと、Integer をきちんと多倍長にしたり、Double などを実装したときに破綻する。また、現状 fail しているほかの件にも影響しているかもしれない。
(showsPrec がダメな件とかあやしい)
### 2020-05-01
以下で、lookupDictArg より先に(なんちゃって)defaulting をしているのがいけないように思われる(src/DictPass.hs l.242-):
$${
simpleTy2dict n2 (TVar y)
| (TVar y) `elem` itvars' = return (Var (DictVar "Prelude.Integer" n2))
| otherwise =
do v <- lookupDictArg (n2, y)
case v of
Nothing -> error ("Error: dictionary not found: "
++ n ++ ", " ++ show (n2,y,itvars))
Just v' -> return (Var v')
$$}
これを、以下のように変更:
$${
simpleTy2dict n2 (TVar y) =
do v <- lookupDictArg (n2, y)
case v of
Just v' -> return (Var v')
Nothing | (TVar y) `elem` itvars' ->
return (Var (DictVar "Prelude.Integer" n2))
| otherwise ->
error ("Error: dictionary not found: "
++ n ++ ", " ++ show (n2,y,itvars))
$$}
この変更で、少なくとも g2.hs のケースは正しくなった:
$${
(Main.main :: (Prelude.IO ())) =
((Prim.putStrLn :: ([Prelude.Char] -> (Prelude.IO ()))) $
(((Prelude.show :: ([Prelude.Show t4] :=> (t4 -> [Prelude.Char])))
${Prelude.Int Prelude.Show}) $
(((Main.g :: ([Prelude.Num t5] :=> (t5 -> t5)))
${Prelude.Int Prelude.Num})
(Main.a :: Prelude.Int))))
(Main.g :: ([Prelude.Num t9] :=> (t9 -> t9))) =
\(Main.g.DARG0 :: Ä) ->
\(_Main.g.U1 :: ([Prelude.Num t6] :=> t6)) ->
((((Prelude.+ :: ([Prelude.Num t7] :=> (t7 -> (t7 -> t7))))
(Main.g.DARG0 :: Å))
(_Main.g.U1 :: ([Prelude.Num t6] :=> t6)))
(1 :: ([Prelude.Num t8] :=> t8)))
(Main.a :: Prelude.Int) =
(9 :: ([Prelude.Num t9] :=> t9))
$$}
メモ: とりあえず、これで少しマシになったのだけど、リテラルを特別に扱っている時点でよくない。これでは fromIntegral などに対処できない。型で処理すべき。
### 2020-05-04
リテラルに由来している ${Num [t] => t} にしか対応していないことで
strlen.hs が動かなかった [054](bissue054) ので、現状の実装を流用しつつ、
これに対応した。
現状では、TcState に tcIntegerTVars という [Type] 型のフィールドを設け、
Num [t] -> t であるところの整数リテラルの型変数をこれに登録していた。
$${
getTy e@(Lit (LitInt _ qt@(_ :=> v))) = do
st <- get
let tvars = tcIntegerTVars st
st' = st{tcIntegerTVars = (v:tvars)}
put st'
return qt
$$}
辞書を探すのに失敗したときには、これを参照して、もし見つかれば
(やっつけ)defaulting で Integer 型としている。
$${
simpleTy2dict n2 (TVar y) =
do v <- lookupDictArg (n2, y)
pss <- getPss
case v of
Just v' -> return (Var v')
Nothing | (TVar y) `elem` itvars' ->
return (Var (DictVar "Prelude.Integer" n2))
| otherwise ->
error ("Error: dictionary not found: "
++ n ++ ", " ++ show (n2,y,pss))
$$}
strlen.hs のようなケースは、Main.strlen の型が ${Num b => [a] -> b}
なので、この型変数 b に相当するものをとらえなくてはいけない。
というわけで、次のようにした。
$${
getTy (Var (TermVar _ qt@(ps :=> _))) =
do checkPreds ps
return qt
where checkPreds :: [Pred] -> TC ()
checkPreds [] = return ()
checkPreds (IsIn n v:ps)
| n == "Prelude.Num" = do st <- get
let tvars = tcIntegerTVars st
put st{tcIntegerTVars = (v:tvars)}
checkPreds ps
| otherwise = checkPreds ps
$$}
解決されない $${Num t => t} を Integer にすることに関しては、
これで一応カバーしているのではないかと思う。(また、問題が見つかるかもしれないけど)
これを「まとも」にするには、いま $${Num t => t} をみつけたら tvars に
型変数を登録しているのをあらため、defaulting 候補になる多相な型変数については、
(型変数のリストでは不十分なので)述語をストックしていき、のちに用いる必要がある。