Save laziness with mapM

consider the following simple IO function:

req :: IO [Integer]
req = do
  print "x"
  return [1,2,3]

In reality, it could be an HTTP request that returns a list after parsing its result.

I am trying to combine the results of several calls to this function in a lazy way.

Simply put, the following should print "x" only two times:

fmap (take 4) req'
--> [1, 2, 3, 4]

I thought this could be resolved with sequenceor mapM, however my approach does not tolerate in terms of laziness:

import Control.Monad

req' :: IO [Integer]
req' = fmap concat $ mapM req [1..1000] -- should be infinite..

This gives the correct result, however, the IO req function is called 1000 times instead of the required 2 times. When implementing the above with a map over an infinite list, the evaluation does not end at all.

+4
source share
5 answers

:

, IO-, ​​ pipes conduit.

:

. , , . , , . , , , , , , ! . .

, . IO, , :

main = do
  yesterdaysDate <- readIORef ref
  writeIORef ref todaysDate

, , , , , - , . , . : , .

, unsafeInterleaveIO:

import System.IO.Unsafe

req :: IO [Integer]
req = unsafeInterleaveIO $ do
    print "x"
    return [1,2,3]

req' :: IO [Integer]
req' = fmap concat $ mapM (const req) [1..1000]

, req , . IO, , , , . IO-, ​​ conduit pipes, .

+6

- streaming pipes. , , . conduit , pipes conduit , streaming; , . streaming ; IO [a] , , . , a Stream (Of Integer) IO () Integer, , -.

req , , , , .

import Streaming
import qualified Streaming.Prelude as S
import Streaming.Prelude (for, each)

req :: Integer -> Stream (Of Integer) IO ()
req x = do                   -- this 'stream' is just a list of Integers arising in IO
  liftIO $ putStr "Sending request #" >> print x
  each [x..x+2]               

req' :: Stream (Of Integer) IO ()
req' = for (S.each [1..]) req -- An infinite succession of requests 
                              -- each yielding three numbers. Here we are not 
                              -- actually using IO to get each but we could.
main = S.print $ S.take 4 req'
-- >>> main
-- Sending request #1
-- 1
-- 2
-- 3
-- Sending request #2
-- 2

, ""; , , req ! S.take req', ; . . Stream (Of Int) IO ()

 type List a = Stream (Of a) IO ()

Haskell, , , . , API Data.List, , , IO . ( , , splitAt, partition chunksOf, , , , , conduit.)

pipes

import Pipes
import qualified Pipes.Prelude as P

req :: Integer -> Producer Integer IO ()
req x = do
  liftIO $ putStr "Sending request #" >> print x
  each [x..x+2]

req' = for (each [1..]) req

main = runEffect $ req' >-> P.take 4 >->  P.print 
-- >>> main
-- Sending request #1
-- 1
-- 2
-- 3
-- Sending request #2
-- 2

, take print , , Data.List. , , . take ing print ing - , , , , - ( - , , - >-> .|, , map.)

, , req

req x = do
  liftIO $ putStr "Sending request #" >> print x
  yield x      -- yield a >> yield b == each [a,b]
  yield (x+1)
  yield (x+2)  

streaming pipes conduit. yield a >> rest a:rest. , yield a ( do) IO, . a <- liftIO readLn; yield a

mapM replicateM traverse sequence - - , . sequence , , . ( sequence = mapM id; mapM f = sequence . map f) , ,

 >>> sequence [getChar,getChar,getChar] >>= mapM_ print
 abc'a'   -- here and below I just type abc, ghci prints 'a' 'b' 'c'
 'b'
 'c'

,

 >>> S.mapM_ print $ S.sequence $ S.each [getChar,getChar,getChar] 
 a'a'
 b'b'
 c'c'

>>> replicateM 3 getChar >>= mapM_ print
abc'a'
'b'
'c'

- - , , Char .

>>> S.mapM_ print $ S.replicateM 3 getChar
a'a'
b'b'
c'c'

. , . replicateM_, mapM_ sequence_ , . , , . - sequence , ,

>>> sequence [Just 1, Just 2, Just 3]
Just [1,2,3]
>>> sequence [Just 1, Just 2, Nothing]
Nothing

Maybe Int, , , Nothing. sequence, mapM, replicateM, traverse , Maybe IO.

, , , :

main = S.toList_ (S.take 4 req') >>= print 
-- >>> main
-- Sending request #1
-- Sending request #2
-- [1,2,3,2]

, pipes:

main = P.toListM (req' >->  P.take 4) >>= print  
-- >>> main
-- Sending request #1
-- Sending request #2
-- [1,2,3,2]

, , IO ,

main = do
  ls <- S.toList_ $ S.print $ S.copy $ S.take 4 req'
  print ls

-- >>> main
-- Sending request #1
-- 1
-- 2
-- 3
-- Sending request #2
-- 2
-- [1,2,3,2]

"" . , , pipes conduit, .

+3

, , , / mapM , , . , , io-streams:

import qualified System.IO.Streams as Streams
import qualified System.IO.Streams.Combinators as Streams

req :: IO (Maybe [Integer])
req = do
  print "x"
  return (Just [1,2,3])

req' :: IO [Integer]
req' = Streams.toList =<< Streams.take 4 =<< Streams.concatLists =<< Streams.makeInputStream req
+1

:

module Foo where

req :: Integer -> IO [Integer]
req _x = do
    print "x"
    return [1,2,3]

req' :: IO [Integer]
req' = concat <$> mapM req [1..1000]

(: fmap concat concat <$>.)

evalute fmap (take 4) req', mapM, , , [1..1000]. , 1000 mapM req - , 1000 Γ— x'-es. concat, (take 4), [1,2,3] 1000 . (take 4) .

, ghci, REPL . , , take 4 thunk , .

, , ( , .) .

, req , , :

module Foo where

req2 :: IO [Integer]
req2 = do
    print "x"
    return [1,2,3]

req2' :: IO [Integer]
req2' = concat <$> mapM (const req2) ([1..1000] :: [Integer])

req2 ' , ( ). , , , , .

0

, . .

#!/usr/bin/env stack
--stack runghc --resolver=lts-7.16 --package pipes

module Main where

import Control.Monad (forever)
import Pipes as P
import qualified Pipes.Prelude as P

req :: Producer Int IO ()
req = forever $ do
  liftIO $ putStrLn "Making a request."
  mapM_ yield [1,2,3]


main :: IO ()
main = P.toListM (req >-> P.take 4) >>= print

Note that you usually don’t collapse the result into a list using pipes, but this is similar to your use case.

0
source

Source: https://habr.com/ru/post/1667572/


All Articles