Bonjour,
je viens de coder un petit truc vite fait en Haskell pour remplir des noms de ville à partir de codes postaux dans un fichier CSV. Ca marche, mais j'aimerais simplifier ma fonction "main" qui contient 2 "case" imbriqués, à cause de la fonction parseCSVFromFile qui a pour type :
parseCSVFromFile :: FilePath -> IO (Either ParseError CSV)
Je fais donc la gestion d'erreur à la main, mais vu que Either est une monade, j'aimerais que ça soit fait automatiquement. Et le fait que le Either soit encapsulé dans un IO complique un peu les choses. J'ai essayé d'utiliser liftM, mais je n'ai pas réussi.
Voici le code en question :
module Main (
main
) where
import Text.CSV
import qualified Data.Map as Map
fromCSV :: CSV -> Map.Map String String
fromCSV = Map.fromList . map (\l -> (l!!2, l!!1)) . init . tail
fillCity :: Map.Map String String -> Record -> Record
fillCity _ l | length l /= 13 = l
fillCity m l = [cp] ++ [Map.findWithDefault "" cp m] ++ drop 2 l
where cp = head l
f :: CSV -> CSV -> String
f cps = printCSV . map (fillCity $ fromCSV cps)
main :: IO ()
main = do
r <- parseCSVFromFile "laposte_hexasmal.csv"
case r of
Left err -> error (show err)
Right cps -> do
r2 <- parseCSVFromFile "listing.csv"
case r2 of
Left err -> error (show err)
Right listing -> putStrLn . f cps $ listing
# deux solutions :
Posté par foobarbazz . Évalué à 2. Dernière modification le 12 mai 2016 à 15:49.
Deux solutions :
Tu peux fusionner les deux case, ça change légèrement la sémantique vis à vis des exceptions :
Avec la monade Either
Sauf erreur de ma part, tu peux pas utiliser de tranformeurs car la monade IO est nécessairement au fond de la pile de monade. Un transformeur mT ne peut que produire une monade IO, là il faudrait une monade IOT qui produit un calcul dans Either
si ça existait il serait possible de sortir de la monade IO, (tu aurais une fonction de type (IO (m a) -> m a), c'est pas bon.
[^] # Re: deux solutions :
Posté par foobarbazz . Évalué à 2.
Honte à moi, j'ai pas vu la jolie solution que tu suggérais pourtant :
Pas du tout besoin de transformeurs, c'est même pas monadique en fait, c'est juste applicatif.
(tes valeurs sont sont encapsulées deux fois, dans (Either ErrorBidule), puis dans IO. Ta fonction f à deux parametre peut être liftée une fois pour travailler sur Either ErrorBidule, et une seconde fois pour travailler sur IO.
[^] # Re: deux solutions :
Posté par max22 . Évalué à 1.
Merci pour ta réponse, je ne pense pas que j'aurais réussi à trouver ça. Je n'ai pas trop l'habitude d'utiliser les liftM2, liftA2, etc. il faudrait que je révise.
tu as créé ton compte juste pour me répondre ? sympa ;)
[^] # Re: deux solutions :
Posté par foobarbazz . Évalué à 2.
Haha, oui et non ^
C'est quelque chose que je voulais régulièrement faire depuis un moment. Voilà, c'est fait :-)
[^] # Re: deux solutions :
Posté par Antoine Catton (site web personnel) . Évalué à 3. Dernière modification le 13 mai 2016 à 21:10.
Ouch… Désolé d'arriver après la bataille, mais le code haskell dont le but est d'être le plus concis possible me rend fou, c'est illisible, et ça donne une mauvaise réputation à Haskell.
Voilà ce que j'écrirais (et je trouve ça 100 fois plus lisible, et c'est beaucoup plus facile à faire évoluer, je vais expliquer pourquoi):
Pourquoi c'est plus lisible (ÀMHA)
La magie est cachée dans
liftParsed
. L'implémentation deliftParsed
est un peu complexe et difficile a lire. Mais tu n'as pas besoin de t'en préoccuper, la seule chose que tu as à lire c'est le typeliftParsed :: IO (Either ParseError a) -> IO a
.Deuxième chose que j'ai modifié c'est le fameux
putStrLn . f cps $ listing
. Je l'ai remplacé parputStrLn $ f cps listing
. « Pourquoi ? » vas-tu me demander. Ce qui important c'est de pouvoir nommer les choses, le$
remplace les parenthèses. Ce qui veux dire que ton expression serait:(putStrLn . f cps) listing
, la mienneputStrLn (f cps listing)
. Les deux fonctionnent, mais pour un lecteur, pour pouvoir comprendre le code, les humains essaient de comprendre ce que les expressions entre parenthèses veulent dire. Une manière de rendre le code plus lisible, serait de donner un nom aux choses entre parenthèse (ce n'est pas toujours nécessaire).Dans mon cas je peux écrire:
Dans ton cas, j'ai vraiment du mal a nommer la variable:
Pourquoi c'est plus facile à faire évoluer?
Prenons le code de foobarbaz:
Imagine demain tu dois parser 3 fichiers. Voilà comment tu vas le faire évoluer:
Je te laisse imaginer le diff avec Git. Et le maximum est
liftM5
, ça veux dire que tu ne peux pas faire un calcul sur plus de 5 fichiers.D'un autre coté avec mon code, voilà le diff avec ma solution:
Edit: J'ai oublié de préciser que le code soumis ici (qui n'est pas celui de foobarbazz) est sous licence WTFPL.
[^] # Re: deux solutions :
Posté par max22 . Évalué à 1.
merci pour ta version, je vais la tester aussi !
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.