newtype
コードでより多くのラッパーを使用して、より明確な型を作成することを検討してきました。また、特に厳密に型指定された構成ファイルの単純な形式として、Read/Show を使用して安価なシリアル化を多数行います。私は今日これに遭遇しました:
この例は次のように始まります。Int をラップする単純な newtype と、アンラップ用の名前付きフィールドを定義します。
module Main where
import Debug.Trace ( trace )
import Text.Read ( readEither )
newtype Bar = Bar { unBar :: Int }
deriving Show
単純な Int 構文からこれらのいずれかを読み取るカスタム インスタンス。ここでのアイデアは、"Bar { unBar = 42 }" の代わりに "42" を構成ファイルに入れることができれば素晴らしいことです。
このインスタンスにはトレース「ログ」もあり、問題を観察するときにこのインスタンスが実際にいつ使用されたかを確認できます。
instance Read Bar where
readsPrec _ s = [(Bar i, "")]
where i = read (trace ("[debug \"" ++ s ++ "\"]") s)
Bar を含む別のタイプになりました。これは Read を自動派生させるだけです。
data Foo = Foo { bar :: Bar }
deriving (Read, Show)
main :: IO ()
main = do
Bar タイプのみの逆シリアル化は正常に機能し、上記の Read インスタンスを使用します
print $ ((readEither "42") :: Either String Bar)
putStrLn ""
しかし、何らかの理由で、Bar を含み、Read に自動的に派生する Foo は、ドリルダウンして Bar のインスタンスを取得しません! (トレースからのデバッグ メッセージも表示されないことに注意してください)
print $ ((readEither "Foo { bar = 42 }") :: Either String Foo)
putStrLn ""
では、Bar のデフォルトの Show フォームはどうですか? デフォルトの Read と一致するはずですよね?
print $ ((readEither "Foo { bar = Bar { unBar = 42 } }") :: Either String Foo)
いいえ!どちらもうまくいきません!! 繰り返しますが、デバッグ メッセージはありません。
実行出力は次のとおりです。
$ stack exec readbug
[debug "42"]
Right (Bar {unBar = 42})
Left "Prelude.read: no parse"
Left "Prelude.read: no parse"
これは私にはバグがあるように見えますが、私が間違っていることを知りたいです。
上記のコードの完全に機能する例が利用可能です。darcshubsrc/Main.lhs
のテスト プロジェクトでファイルを参照してください