2
votes

Évaluation paresseuse des actions IO

J'essaie d'écrire du code dans le style source -> transform -> sink , par exemple:

-- prints the number of lines entered before "quit" is entered
[getLine..] >>= takeWhile (/= "quit") >>= length >>= print

Mais j'aimerais le faire en utilisant IO . J'ai cette impression que ma source peut être une liste infinie d'actions IO, et chacune est évaluée une fois qu'elle est nécessaire en aval. Quelque chose comme ceci:

let (|>) = flip ($)
repeat 1 |> take 5 |> sum |> print

Je pense que c'est possible avec les bibliothèques de streaming, mais est-ce que cela peut être fait dans le sens de ce que je propose?

p>


1 commentaires

En général, oui. C'est exactement le genre de chose que les bibliothèques pipes et conduit (et plusieurs autres aussi) doivent faire. Je n'ai pas d'expérience avec l'un ou l'autre moi-même, donc les exemples devront venir des autres.


3 Réponses :


0
votes

Cela semble être dans cet esprit:

let (|>) = flip ($)
let (.>) = flip (.)

getContents >>= lines .> takeWhile (/= "quit") .> length .> print


1 commentaires

Oui, mais je vois getContents comme une action paresseuse. Je suis particulièrement intéressé par plusieurs actions.



1
votes

Utilisation de repeatM , takeWhile et length_ fonctions de bibliothèque en streaming :

import Streaming
import qualified Streaming.Prelude as S

count :: IO ()
count = do r <- S.length_ . S.takeWhile (/= "quit") . S.repeatM $ getLine
           print r


2 commentaires

Connexes: stackoverflow.com/questions/47133634/… stackoverflow.com/questions/40317431/... < a href = "https://stackoverflow.com/questions/50178815/can-io-actions-be-sequenced- while-keeping-the-logic-in-a-pure-function/50181766#50181766" title = "peut Les actions io doivent être séquencées tout en conservant la logique dans une fonction pure "> stackoverflow.com/questions/50178815/...


Bien sûr, cela fonctionnera, mais ma question est de savoir pourquoi ai-je besoin d'une bibliothèque de streaming? Pourquoi la paresse "normale" de Haskell n'est-elle pas suffisante ici?



0
votes

Le problème ici est que Monad n'est pas la bonne abstraction pour cela, et essayer de faire quelque chose comme ça entraîne une situation où la transparence référentielle est brisée.

Premièrement, nous pouvons faire un Lazy IO se lit comme suit:

1
Hello World

Cette fois, exécutera cat . Il lira les lignes et les affichera. Tout fonctionne bien.

Mais pensez à changer la fonction principale en ceci:

main :: IO ()
main = do
    x <- lazyIOSequence (map (putStrLn . show) [1..])
    seq (head x) putStrLn "Hello World"

Cela ne produit que Hello World , car nous n'avions pas besoin pour évaluer l'un des l . Mais maintenant, envisagez de remplacer la dernière ligne comme suit:

main :: IO ()
main = do
    l <- lazyIOSequence (map (putStrLn . show) [1..])
    putStrLn "Hello World"

Même programme, mais le résultat est maintenant:

module Main where

import System.IO.Unsafe (unsafePerformIO)
import Control.Monad(forM_)

lazyIOSequence :: [IO a] -> IO [a]
lazyIOSequence = pure . go where
    go :: [IO a] -> [a]
    go (l:ls) = (unsafePerformIO l):(go ls)


main :: IO ()
main = do
    l <- lazyIOSequence (repeat getLine)
    forM_ l putStrLn 

C'est mauvais, nous avons changé les résultats d'un programme simplement en évaluant une valeur. Cela n'est pas censé se produire dans Haskell, lorsque vous évaluez quelque chose, il devrait simplement l'évaluer, pas changer le monde extérieur.

Donc, si vous limitez vos actions d'E / S à quelque chose comme la lecture d'un fichier, rien d'autre ne lit à partir de, alors vous pourrez peut-être évaluer paresseusement les choses de manière raisonnable, car lorsque vous le lisez par rapport à toutes les autres actions d'E / S prises par votre programme, cela n'a pas d'importance. Mais vous ne voulez pas autoriser cela pour les E / S en général, car sauter des actions ou les exécuter dans un ordre différent peut avoir de l'importance (et au-dessus, certainement). Même dans le cas de la lecture paresseuse d'un fichier, si quelque chose d'autre dans votre programme écrit dans le fichier, le fait que vous évaluiez cette liste avant ou après l'action d'écriture affectera la sortie de votre programme, ce qui, encore une fois, rompt la transparence référentielle (car l'ordre d'évaluation ne devrait pas avoir d'importance).

Donc, pour un sous-ensemble restreint d'actions IO, vous pouvez définir judicieusement Functor, Applicative et Monad sur un type de flux pour fonctionner de manière paresseuse, mais le faire dans IO Monad en général est un champ de mines et souvent tout simplement incorrect. Au lieu de cela, vous voulez un type de streaming spécialisé, et en fait Conduit définit Functor, Applicative et Monad sur beaucoup de ses types afin que vous puissiez toujours utiliser toutes vos fonctions préférées.


0 commentaires