J'ai exploré l'utilisation de plusieurs enveloppes newtype
dans mon code pour créer des types plus distincts. Je fais aussi beaucoup de sérialisation bon marché en utilisant Read/Show, particulièrement comme une simple forme de fichier de configuration fortement typé. Je suis tombé sur aujourd'hui:Instance de classe de type non utilisée lors de la dérivation contenant une structure de données
L'exemple commence comme ça, et je définir simple newtype à enrouler autour de Int, avec un champ nommé pour déballer:
module Main where
import Debug.Trace (trace)
import Text.Read (readEither)
newtype Bar = Bar { unBar :: Int }
deriving Show
exemple sur mesure pour lire un de ces de une syntaxe Int simple. L'idée ici est que ce serait génial de pouvoir mettre "42" dans un fichier de configuration au lieu de "Bar {unBar = 42}"
Cette instance a aussi une trace "logging" donc on peut voir quand cette instance est vraiment utilisé lors de l'observation du problème.
instance Read Bar where
readsPrec _ s = [(Bar i, "")]
where i = read (trace ("[debug \"" ++ s ++ "\"]") s)
Maintenant un autre type contenant une barre. Celui-ci va juste auto-dériver Read.
data Foo = Foo { bar :: Bar }
deriving (Read, Show)
main :: IO()
main = do
désérialisation le type de bar fonctionne seul bien et utilise l'instance Lire ci-dessus
print $ ((readEither "42") :: Either String Bar)
putStrLn ""
Mais pour une raison quelconque Foo, contenant une barre, et dérivé automatiquement en lecture, ne fore vers le bas et ramasser Les instances de Bar! (Notez que le message de débogage de trace ne soit affiché)
print $ ((readEither "Foo { bar = 42 }") :: Either String Foo)
putStrLn ""
Alors ok, pourquoi la valeur par défaut Afficher sous forme de barre, doit correspondre à la valeur par défaut Lire droit?
print $ ((readEither "Foo { bar = Bar { unBar = 42 } }") :: Either String Foo)
Non! Ne fonctionne pas non plus !! Encore une fois, pas de message de débogage.
est ici la sortie d'exécution:
$ stack exec readbug
[debug "42"]
Right (Bar {unBar = 42})
Left "Prelude.read: no parse"
Left "Prelude.read: no parse"
Cela semble buggy pour moi, mais j'aimerais entendre que je le fais mal.
Un exemple complet du code ci-dessus est disponible. Voir le fichier src/Main.lhs
dans un test project on darcshub
C'est une très bonne question. J'adore la facilité avec laquelle vous avez commencé à déboguer votre code. J'espère que ma réponse est utile pour identifier le problème particulier que vous rencontrez. Cela mis à part, je ne recommanderais jamais d'utiliser 'Read' pour autre chose que le débogage - et ensuite assurez-vous que' read. show = id'. Je voudrais mettre ma config dans un JSON (et utiliser 'aeson' pour encoder/décoder), ou (si vous insistez sur un analyseur personnalisé) utiliser quelque chose comme' attoparsec' ou 'megaparsec'. 'Read' est un analyseur phénoménalement inefficace, car il est prêt à faire marche arrière n'importe où. – Alec
Vous avez tort: l'instance dérivée de 'Foo' * est * en utilisant l'instance' Read' de 'Bar' que vous avez écrite! C'est juste que l'instance 'Foo' échoue avant qu'elle ne dérange pour forcer la valeur de' Bar' (donc ne force jamais le thunk avec la 'trace' dedans), parce que' Bar' signale incorrectement qu'elle a consommé toutes les entrées restantes et le lecteur 'Foo' ne voit donc pas le'} 'dont il a besoin pour réussir. –
@Alec Je n'avais pas envisagé d'utiliser JSON pour les configs. Conserve la structure typographique et hiérarchique. Et puis vous vous retrouvez avec un fichier de configuration qui est utilisable par d'autres langages/systèmes. Je vais explorer cela avec newtypes un peu maintenant. Merci! – dino