# 067: showlist2.hs がランタイムに abend [↑up](bunny_notes) - issued: 2020-05-08 - 分類: A サンプルコードが fail - status: Closed (2020-05-13) ## 概要 [055](bissue055) の調査のために、再現用の小さなテストプロを作成。 showlist2.hs: $$
{ $ cat testcases/showlist2.hs main = do putStrLn $ showList [1] "" where showList xs = (++) (showlist' xs) where showlist' [] = "[]" showlist' [x] = "[" ++ show x ++ "]" showlist' (x:xs) = "[" ++ foldl (\s t -> s ++ "," ++ show t) (show x) xs ++ "]" $$} これをコンパイル、実行するとランタイムでエラーする。 なお、これに似た showlist.hs は大丈夫。 showlist.hs: $${ main = do putStrLn $ showList [1] "" where showlist' [] = "[]" showlist' [x] = "[" ++ show x ++ "]" showlist' (x:xs) = "[" ++ foldl (\s t -> s ++ "," ++ show t) (show x) xs ++ "]" showList xs = (++) (showlist' xs) $$} また、showlist2.hs の showlist' に型注釈をつけたもの(showlist3.hs)も Ok. エラーするケースでは、showlist' 内部における辞書の解決がおかしいようにみえる。 let 式がネストされているケースの多相の解決(DictPass による辞書渡し形式への変換) に難があるようだ。flesh な type value にすべきところをしていないとか、かも。 うまくいくケースとそうでない場合の core0, core を見比べてみよう。 ## 調査ログ ### 2020-05-12 だめな場合の core を確認すると、DARG0 を渡すべきところで、${Prelude.Char Prelude.Show} を渡しているよう。型注釈をつけた showlist3.hs では、 ${Prelude.Integer Prelude.Show} がその場で生成されているので OK。 ただ、まだ例が大きすぎてみるのが大変だし、被疑個所が特定しづらい。 そこで、もっと小さくしていって、エラーが出る場合と出ない場合の境界をあきらかにしたい。 どうやら、これが最小再現ケースではないかな (t067.hs): $${ main = do putStrLn $ f 1 "" where f xs str = str ++ (show' xs) where show' x = show x $$} show' はここまで単純でも再現する。でも、let が nest されていて、show' の型が決まらない(多相になる)のは必須だと思われる。 instancee (Show a) => Show [a] の実装において発覚した問題だが、リストは関係なかったよう。また、元の例ではパターンマッチコンパイル結果の case 式が頻出していて、 その複雑な場合に発生する問題かなと恐れたのですが、そうでもなかった。 なお、${f} はこれ以上単純化する、たとえば、$ {f xs str = show' xs} のようにすると、もう、再現しない。 t067.hs の問題の core は次の通り: $$ { ---- ddumpCore ---- (Main.main :: (Prelude.IO ())) = let (Main.l1.l0.f :: ([Prelude.Show t5] :=> (t5 -> ([Prelude.Char] -> [Prelude.Char])))) = \(Main.l1.l0.f.DARG0 :: Ä) -> \(_Main.l1.l0.f.U1 :: ([Prelude.Show t0] :=> t0)) (_Main.l1.l0.f.U2 :: ([Prelude.Show t0] :=> [Prelude.Char])) -> let (Main.l1.l0.l0.l0.show' :: ([Prelude.Show t3] :=> (t3 -> [Prelude.Char]))) = \(Main.l1.l0.l0.l0.show'.DARG0 :: Ä) -> \(_Main.l1.l0.l0.l0.show'.U1 :: ([Prelude.Show t1] :=> t1)) -> (((Prelude.show :: ([Prelude.Show t2] :=> (t2 -> [Prelude.Char]))) ${Prelude.Char Prelude.Show}) (_Main.l1.l0.l0.l0.show'.U1 :: ([Prelude.Show t1] :=> t1))) in (((Prelude.++ :: ([t3] -> ([t3] -> [t3]))) (_Main.l1.l0.f.U2 :: ([Prelude.Show t0] :=> [Prelude.Char]))) (((Main.l1.l0.l0.l0.show' :: ([Prelude.Show t4] :=> (t4 -> [Prelude.Char]))) (Main.l1.l0.f.DARG0 :: Å)) (_Main.l1.l0.f.U1 :: ([Prelude.Show t0] :=> t0)))) in ((Prim.putStrLn :: ([Prelude.Char] -> (Prelude.IO ()))) $ ((((Main.l1.l0.f :: ([Prelude.Show t7] :=> (t7 -> ([Prelude.Char] -> [Prelude.Char])))) ${Prelude.Integer Prelude.Show}) (((Prelude.fromInteger :: ([Prelude.Num t8] :=> (Prelude.Integer -> t8))) ${Prelude.Integer Prelude.Num}) (1 :: Prelude.Integer))) "")) $$} ${Main.l1.l0.l0.l0.show'} のなかで、show に渡されている辞書が、 なぜ唐突に $ {${Prelude.Char Prelude.Show}} なのかということ。 show' の外にある、f を単純にすると再現しないことから、ひとつ上のレベルの変な情報を拾ってしまっている模様。(そういえば、CompositDict を思いつくまえに、変な改造をいれたかな…) f を少し単純にした t067b.hs のコアは以下のとおり: $$ { ---- ddumpCore ---- (Main.main :: (Prelude.IO ())) = let (Main.l1.l0.f :: ([Prelude.Show t5] :=> (t5 -> (t6 -> [Prelude.Char])))) = \(Main.l1.l0.f.DARG0 :: Ä) -> \(_Main.l1.l0.f.U1 :: ([Prelude.Show t0] :=> t0)) (_Main.l1.l0.f.U2 :: ([Prelude.Show t0] :=> t1)) -> let (Main.l1.l0.l0.l0.show' :: ([Prelude.Show t4] :=> (t4 -> [Prelude.Char]))) = \(Main.l1.l0.l0.l0.show'.DARG0 :: Ä) -> \(_Main.l1.l0.l0.l0.show'.U1 :: ([Prelude.Show t2] :=> t2)) -> (((Prelude.show :: ([Prelude.Show t3] :=> (t3 -> [Prelude.Char]))) (Main.l1.l0.l0.l0.show'.DARG0 :: Å)) (_Main.l1.l0.l0.l0.show'.U1 :: ([Prelude.Show t2] :=> t2))) in (((Main.l1.l0.l0.l0.show' :: ([Prelude.Show t4] :=> (t4 -> [Prelude.Char]))) (Main.l1.l0.f.DARG0 :: Å)) (_Main.l1.l0.f.U1 :: ([Prelude.Show t0] :=> t0))) in ((Prim.putStrLn :: ([Prelude.Char] -> (Prelude.IO ()))) $ ((((Main.l1.l0.f :: ([Prelude.Show t7] :=> (t7 -> (t8 -> [Prelude.Char])))) ${Prelude.Integer Prelude.Show}) (((Prelude.fromInteger :: ([Prelude.Num t9] :=> (Prelude.Integer -> t9))) ${Prelude.Integer Prelude.Num}) (1 :: Prelude.Integer))) "")) $$} 上手くいっている方は、_Main.l1.l0.f.U2 の型が違う(多相)。 だめな方は、これをなぜか拾っている?? おお、そうか。_Main.l1.l0.f.U1 と _Main.l1.l0.f.U2 の量化変数がともに t0 になっていて、 まざっているんだと思われる。 Core0 の時点でおかしいので、問題は DictPass ではなく、TrCore あたりにある。 $${ ---- ddumpCore ---- (Main.main :: (Prelude.IO ())) = let (Main.l1.l0.f :: ([Prelude.Show t5] :=> (t5 -> ([Prelude.Char] -> [Prelude.Char])))) = \(_Main.l1.l0.f.U1 :: ([Prelude.Show t0] :=> t0)) (_Main.l1.l0.f.U2 :: ([Prelude.Show t0] :=> [Prelude.Char])) -> $$} ちょっと怖いので、ブランチをわけて修正作業をしよう。あとで。 ### TrCore 修正 上記の件で、TrCore を修正。_Main.l1.l0.f.U2 の型から不要な Predicate は消えるようになったが、辞書渡しがおかしいのは変わらず。 $${ $ git diff diff --git a/compiler/src/TrCore.hs b/compiler/src/TrCore.hs index 291a029..5abc145 100644 --- a/compiler/src/TrCore.hs +++ b/compiler/src/TrCore.hs @@ -6,7 +6,7 @@ import PreDefined (ConstructorInfo, initialConsts) import Symbol import Types import Typing (Assump (..), Pred (..), Qual (..), - Scheme (..), find, inst) + Scheme (..), find, inst, tv) import qualified Typing as Ty (Expr (..), Literal (..), Pat (..)) @@ -120,8 +120,9 @@ transVdef (n, Pat.Lambda ns expr) = do vs = zipWith f ns ts qf = case qt of (qf' :=> _) -> qf' - f n' t' = TermVar n' (qf :=> t') - as' = [n' :>: Forall [] (qf :=> t') | (n', t') <- zip ns ts] + f n' t' = let qf' = filter (\pr -> head (tv pr) `elem` tv t') qf + in TermVar n' (qf' :=> t') + as' = [n' :>: Forall [] (qf' :=> t') | TermVar n' (qf' :=> t') <- vs] appendAs as' expr' <- transExpr expr appendBind (n, lam' vs expr') $$} このあたりの実装は、最後あたりに整理しなおさないといけなさそう。 あやふやな理解のまま書いた風なところが多い。 もしかして、DictPass では、Lambda の型を assump からとればいい (TrCore で登録してある)のに、TrCore の古いバージョンと同じやり方で生成しているのでは? ### DictPass 調査(継続) ${tcExpr e@(Var n (qv :=> t'))) qt} にトレースを仕掛けてみると、 ここで問題の "show" 呼び出しを処理するときに与えられる qt が変。 $$ { (Var (TermVar "Prelude.show" ([IsIn "Prelude.Show" (TVar (Tyvar "t2" Star))] :=> TAp (TAp (TCon (Tycon "(->)" (Kfun Star (Kfun Star Star)))) (TVar (Tyvar "t2" Star))) (TAp (TCon (Tycon "Prelude.[]" (Kfun Star Star))) (TCon (Tycon "Prelude.Char" Star))))),[] :=> TAp (TAp (TCon (Tycon "(->)" (Kfun Star (Kfun Star Star)))) (TCon (Tycon "Prelude.Char" Star))) (TAp (TCon (Tycon "Prelude.[]" (Kfun Star Star))) (TCon (Tycon "Prelude.Char" Star)))) $$} これでは、show の第一引数の型は Char なので、${Prelude.Char Prelude.Show} が生成される。 ### 被疑箇所? 仮に、Let の tcExpr 中で、tcBind に state を引き継がないようにすると、 t067.hs は通るようになる(他がだめになるが)。 let の中と外でなにかコンフリクトしている模様。 $${ tcExpr (Let bs e) qt = do st <- get let bs' = tcBind bs (tcCe st) (Just st) e' <- tcExpr e (normQTy qt) return $ Let bs' e' $$} tcBind にせっかく状態をわたしているのに、そこで更新された tcNum をあとに引き継いでなかった(複数の tcbind 間でも)のを修正。 それで改善するかと思ったが、そうはいかず。 でも、この修正は必要だったとは思われる。 ### 2020-05-13 t067.hs 処理のどこで初めて t1 なり t2 が Char になってしまうか捉えるために、 check&stoper を仕掛ける。 $${ -- checkStoper (for 067) checkStop :: TC () checkStop = do s <- getSubst let t1 = TVar (Tyvar "t1" Star) t2 = TVar (Tyvar "t2" Star) t1' = apply s t1 t2' = apply s t2 trace (show (t1,t1',t2,t2')) $ return () when (t1' == tChar) $ error "checkStop t1' = tChar" when (t2' == tChar) $ error "checkStop t2' = tChar" $$} tcExpr Lam における unify' 直後の checkStop で停止した: $${ tcExpr e@(Lam vs ebody) (qs :=> t) = do trace ("tcExpr 0:" ++ show e ++ " (" ++ show qs ++ " :=> " ++ show t ++ ")") checkStop _ :=> t' <- getTy e unify' t' t trace (show t') $ return () trace ("tcExpr 1:" ++ show e ++ " (" ++ show qs ++ " :=> " ++ show t ++ ")") checkStop qt <- getTy ebody s <- getSubst ebody' <- tcExpr ebody (normQTy (apply s qt)) return $ Lam vs ebody' $$} つかんだ尻尾は以下: $${ TAp (TAp (TCon (Tycon "(->)" (Kfun Star (Kfun Star Star)))) (TVar (Tyvar "t1" Star))) (TAp (TCon (Tycon "Prelude.[]" (Kfun Star Star))) (TCon (Tycon "Prelude.Char" Star))) tcExpr 1: Lam [TermVar "_Main.l1.l0.l0.l0.show'.U1" ([IsIn "Prelude.Show" (TVar (Tyvar "t1" Star))] :=> TVar (Tyvar "t1" Star))] (App (Var (TermVar "Prelude.show" ([IsIn "Prelude.Show" (TVar (Tyvar "t2" Star))] :=> TAp (TAp (TCon (Tycon "(->)" (Kfun Star (Kfun Star Star)))) (TVar (Tyvar "t2" Star))) (TAp (TCon (\ Tycon "Prelude.[]" (Kfun Star Star))) (TCon (Tycon "Prelude.Char" Star)))))) (Var (TermVar "_Main.l1.l0.l0.l0.show'.U1" ([IsIn "Prelude.Show" (TVar (Tyvar "t1" Star))] :=> TVar (Tyvar "t1"\ Star))))) ([IsIn "Prelude.Show" (TVar (Tyvar "t3" Star))] :=> TAp (TAp (TCon (Tycon "(->)" (Kfun Star (Kfun Star Star)))) (TVar (Tyvar "t3" Star))) (TAp (TCon (Tycon "Prelude.[]" (Kfun Star Star))) (TCon (Tycon "Prelude.Char" Star)))) bunnyc: checkStop t1' = tChar $$} ここでは、t1 -> [Char] と t3 -> [Char] を unify して、t1 -> Char に なってしまっている。 しらべると、この時点ですでに t3 -> Char となっていた。 なので、こんどは、t3 -> Char になった瞬間を捕まえなくてはいけない。 おっていくと、(++) :: [t3] -> [t3] -> [t3] で、これの引数に "" が与えられているので、 t3 -> Char と置換されるのは正しい。 問題は、show' に出現する show の型にも同じ型変数が用いられ、show :: (Show t3) => t3 -> [Char] となっているところ。 やっぱり、被疑個所は DictPass ではない。 ## 原因判明 TrCore.appendBind に問題があった。 以下は修正後のコード: $${ appendBind :: (Id, Expr) -> TRC () appendBind (n, e) = do t <- typeLookup n st <- get -- this must be after the typeLookup above. let Rec bs = trcBind st bs' = bs ++ [(TermVar n t, e)] put st{trcBind = Rec bs'} $$} typeLookup にて、Assump から識別子 n の型を検索し、かつ、 量化変数を具体化するのだが、そこで状態 st が更新される。 が、従来のコードは、st <- get を typelookup の前に行っていたため、 その後の put st{...} で、st の内部状態 trcNum が巻き戻ってしまい、 番号の重複につながっていた(t3 が二回生成されて衝突)。 これを直したことで、本件の問題は解決。 - showlist2.hs : sample194 - t067.hs : sample195 もともと ok だった、showlist.hs, showlist3.hs は廃止。