2012-09-11 1 views
3

J'ai écrit ce code:Itérer dans un do bloc

toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode 
        outputFile <- openFile "couples.txt" WriteMode 
        readAndChange inputFile outputFile 

readAndChange i o = do iseof <- hIsEOF i 
         if iseof then (return o) 
         else do line <- hGetLine i 
           hPutStrLn o (show (extractNameAndId line)) 
           readAndChange i o 

Je me demande si je peux réécrire ce code en utilisant seulement une fonction, en utilisant quelque chose de similaire à ce modèle:

function x = do ... 
       label 
       ..... 
       if ... then label else exit 

Répondre

12

Vous rendez la vie difficile en programmant d'une manière absolument inutile. Vous programmez dans la belle langue Haskell et vous cherchez une construction goto!

Pourquoi ne pas simplement import Control.Applicative (<$>) et écrire

readAndChange' = writeFile "couples.txt" =<< 
    unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv" 

(Eh oui, c'est presque en une ligne. Il est dans un style propre et fonctionnel et épuré par la mécanique de la lecture et des lignes d'écriture. Autant que possible du le traitement se fait dans le code pur, seule entrée et de sortie sont basés-IO)

Explication:.

ici unlines.map (show.extractNameAndId).lines traite votre entrée en coupant i t en lignes, en appliquant extractNameAndId puis show à chacun en utilisant map, puis en les rejoignant à nouveau avec unlines.

unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv" va lire le fichier et appliquer la fonction de traitement. <$> est une syntaxe agréable pour fmap.

writeFile "couples.txt" =<< getanswer est la même que getanswer >>= writeFile "couples.txt" - obtenir la réponse comme ci-dessus puis l'écrire dans le fichier.

Essayez d'écrire greet xs = "hello " ++ xs puis à ghci faire ce pour le plaisir

greet "Jane"  -- apply your pure function purely 
greet $ "Jane"  -- apply it purely again 
greet <$> ["Jane","Craig","Brian"] -- apply your function on something that produces three names 
greet <$> Just "Jane"    -- apply your function on something that might have a name 
greet <$> Nothing     -- apply your function on something that might have a name 
greet <$> getLine     -- apply your function to whatever you type in 
greet <$> readFile "deletedId.csv" -- apply your function to your file 

le dernier est comment nous avons utilisé <$> dans readAndChange. S'il y a beaucoup de données dans deletedId.csv vous allez manquer le bonjour, mais bien sûr, vous pouvez faire

greet <$> readFile "deletedId.csv" >>= writeFile "hi.txt" 
take 4.lines <$> readFile "hi.txt" 

pour voir les 4 premières lignes.

Donc $ vous permet d'utiliser votre fonction sur les arguments que vous avez donnés. greet :: String -> String donc si vous écrivez greet $ person, le person doit être de type String, alors que si vous écrivez greet <$> someone, le someone peut être quelque chose qui produit une String - une liste de chaînes, une IO String, un Maybe String. Techniquement, someone :: Applicative f => f String, mais vous devriez d'abord lire sur les classes de type et les foncteurs applicatifs. Vous apprendre un Haskell pour le grand bien est une excellente ressource.

Pour encore plus de plaisir, si vous avez une fonction avec plus d'un argument, vous pouvez toujours utiliser le joli style Applicative.

insult :: String -> String -> String 
insult a b = a ++ ", you're almost as ugly as " ++ b 

Essayez

insult "Fred" "Barney" 
insult "Fred" $ "Barney" 
insult <$> ["Fred","Barney"] <*> ["Wilma","Betty"] 
insult <$> Just "Fred" <*> Nothing 
insult <$> Just "Fred" <*> Just "Wilma" 
insult <$> readFile "someone.txt" <*> readFile "someoneElse.txt" 

Ici vous utilisez <$> après la fonction et <*> entre les arguments dont il a besoin. Comment cela fonctionne est un peu hallucinant au premier abord, mais c'est le style le plus fonctionnel de l'écriture efficace des calculs.

Prochaine lecture sur les foncteurs applicatifs. Ils sont super.
http://learnyouahaskell.com/functors-applicative-functors-and-monoids
http://en.wikibooks.org/wiki/Haskell/Applicative_Functors

+0

Incroyable :) Comme vous pouvez le deviner je suis un débutant Haskell; Pouvez-vous s'il vous plaît m'expliquer s'il y a une différence entre '$' et '<$>'? – Aslan986

+0

@ Aslan986 J'ai ajouté un peu plus maintenant. – AndrewC

+0

Impossible de ne pas accepter votre réponse. Merci beaucoup Andrew. – Aslan986

3
import Control.Monad 
import Control.Monad.Trans 
import Control.Monad.Trans.Either 

readAndChange i o = do 
    result <- fmap (either id id) $ runEitherT $ forever $ do 
     iseof <- lift $ hIsEof i 
     when iseof $ left o -- Break and return 'o' 
     line <- lift $ hGetLine i 
     lift $ hPutStrLn o $ show $ extractNameAndId line 
    -- 'result' now contains the value of 'o' you ended on 
    doSomeWithingWith result 

Pour comprendre pourquoi cette technique fonctionne, lisez this.

3

Vous pouvez faire récursivité en utilisant modèle let, qui est toutefois similaire à la fonction de définition de récursion séparément:

main = do 
    let x = 10 
    let loop = do 
     print 1 
     when (x<20) loop 
    loop 

Vous pouvez utiliser fix de Control.Monad.Fix aussi pour obtenir un comportement similaire

main = do 
    let x = 10 
    fix $ \loop -> do 
     print 1 
     when (x<20) loop 

Qu'est-ce que vous indiquent sont une sorte de modèle goto label. Je ne sais pas si vous pouvez obtenir ce genre de comportement, mais l'utilisation ci-dessus de fix ou let peut facilement vous aider à atteindre la récursivité.

[modifier] Il y a quelques autres modèles pour obtenir des résultats similaires à l'aide de la Cont monade comme dans

getCC' :: MonadCont m => a -> m (a, a -> m b) 
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f)) 

main = (`runContT` return) $ do 
    (x, loopBack) <- getCC' 0 
    lift (print x) 
    when (x < 10) (loopBack (x + 1)) 
    lift (putStrLn "finish") 

qui imprime les numéros de 1 to 10. Voir goto using continuation pour une meilleure explication.

Il existe également une monade et un transformateur Goto. Je ne l'ai pas utilisé. Vous pourriez le trouver adapté à votre besoin.

+1

Personnellement, j'aime beaucoup l'approche 'fix'. Notez qu'il est moralement équivalent à l'approche "recusive' let' ", puisque' fix' est une façon de définir la récursivité. (En fait, je viens de changer une partie de mon code pour utiliser 'fix' au lieu d'un' let' récursif.) Notez aussi que 'fix' est aussi disponible dans' Data.Function'; il n'y a rien de monadique à ce sujet. 'Control.Monad.Fix' * fournit aussi la classe de type' MonadFix', qui fournit 'mfix :: MonadFix m => (a -> m a) -> m a'. –

+1

Pour tous ceux qui aiment l'approche "combinateur en boucle", vous pouvez trouver des boucles plus sophistiquées que simplement "fix" dans [le paquetage monad-loops] (http://hackage.haskell.org/package/monad-loops) . C'est plutôt joli. –

0

Contrairement aux langages de programmation impératifs, et aussi contrairement à d'autres langages de programmation fonctionnelle, Haskell ne comporte aucune construction syntaxique pour l'écriture pour-boucles ou tout en boucles, ce qui est ce que vous semblez demander ici. L'idée est que les processus récursifs et les processus itératifs peuvent être capturés uniformément par des fonctions récursives. C'est juste que les processus itératifs sont capturés comme des types particuliers de fonctions récursives: ces fonctions sont queue récursive. Le code impératif tel que celui qui apparaît dans do-blocks ne fait pas exception. Vous pourriez trouver ce manque de construction de boucle explicite ennuyeux parce que vous devez définir une nouvelle fonction pour chaque boucle, d'où le besoin de nommer la boucle. Cependant, cela est un prix trivial à payer pour l'uniformité et la simplicité de l'approche Haskell, pour trois raisons:

  1. Vous ne devez pas définir la fonction qui représente une boucle à haut niveau. Vous pouvez le définir localement. Dans Haskell, beaucoup de gens utilisent généralement toujours le même nom pour ce type de boucle. Les choix populaires ici sont go et aux.Votre code pourrait donc être réécrite comme suit:

    toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode 
            outputFile <- openFile "couples.txt" WriteMode 
            let go = do 
             iseof <- hIsEOF inputFile 
             if iseof then (return outputFile) 
             else do line <- hGetLine inputFile 
               hPutStrLn outputFile (show (extractNameAndId line)) 
               go 
            go 
    
  2. Enfin, l'absence de constructions en boucle est assez immatérielle parce que souvent on n'a pas besoin d'écrire des boucles du tout. Dans votre cas, d'autres réponses dans ce fil ont montré plusieurs façons de le faire.

1

La première chose que vous devez faire est de lire la documentation du module Control.Monad, qui est une nécessité absolue pour l'écriture de code Haskell. Pendant que vous y êtes, installez le paquet Control.Monad.Loops de Hackage, et lisez les docs dessus; vous pourriez être particulièrement intéressé par la fonction whileM_ il:

import Data.Functor ((<$>) 
import Control.Monad.Loops (whileM_) 

readAndChange i o = 
    whileM_ notEOF $ do line <- hGetLine i 
         hPutStrLn o (show (extractNameAndId line)) 
     where notEOF = not <$> (hIsEOF i) 

La bibliothèque en question met en œuvre comme ce whileM_, qui est le modèle que vous recherchez:

-- |Execute an action repeatedly as long as the given boolean expression 
-- returns True. The condition is evaluated before the loop body. 
-- Discards results. 
whileM_ :: (Monad m) => m Bool -> m a -> m() 
whileM_ p f = do 
     x <- p 
     if x 
       then do 
         f 
         whileM_ p f 
       else return() 

encore, je dois convenir que vous écrivez ceci d'une manière excessivement impérative. Essayez de penser ainsi: votre programme transforme fondamentalement une chaîne d'entrée en une chaîne de sortie. Cela suggère immédiatement que le cœur de la logique de votre programme devrait avoir ce type:

transformInput :: String -> String 
transformInput = ... 

Votre produit de transformation en une ligne par ligne. Cela signifie que vous pouvez affiner le croquis de cette façon (la fonction lines divise une chaîne en lignes, la liste unlines Rejoins):

transformInput :: String -> String 
transformInput input = unlines (map transformLine (lines input)) 

transformLine :: String -> String 
transformLine line = show (extractNameAndId line) 

Maintenant, vous avez le cœur de la logique dans la fonction transformInput, de sorte que vous venez besoin de raccorder cela aux poignées d'entrée et de sortie. Si vous traitez avec stdin et stdout, vous pouvez utiliser la fonction interact pour le faire. Mais nous pouvons réellement voler sa mise en œuvre et le modifier:

hInteract  :: Handle -> Handle -> (String -> String) -> IO() 
hInteract i o f = do s <- hGetContents i 
         hPutStr o (f s) 

Et maintenant, voilà:

toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode 
        outputFile <- openFile "couples.txt" WriteMode 
        hInteract inputFile outputFile transformInput 

Attention: tout code non testé.


Une dernière chose, dans l'intérêt de la divulgation complète: l'astuce ici est que hGetContents effectue I/O paresseux: il vous permet essentiellement de traiter tout le contenu d'une poignée comme String, et donc appliquez la fonction transformInput au contenu de la poignée. Mais tout est fait paresseusement, il n'a donc pas besoin de lire tout le fichier à la fois.

C'est la manière la plus facile de le faire, et vous devriez l'apprendre, mais elle a un gros défaut, c'est que vous pouvez perdre le contrôle sur la fermeture des poignées. Pour les programmes rapides et sales, c'est correct, cependant.

Questions connexes