Home > Enterprise >  How to get this function to be evaluated lazily
How to get this function to be evaluated lazily

Time:11-19

I have the following function:

main = do xs <- getContents
          edLines <- ed $ lines xs
          putStr $ unlines edLines

Firstly I used the working version main = interact (unlines . ed . lines) but changed the signature of ed since. Now it returns IO [String] instead of just [String] so I can't use this convenient definition any more.

The problem is that now my function ed is still getting evaluated partly but nothing is displayed till I close the stdin via CTRL D.

Definition of ed:

ed :: Bool -> [EdCmdLine] -> IO EdLines
ed xs = concatM $ map toLinesExt $ scanl (flip $ edLine defHs) (return [Leaf ""]) xs where
    toLinesExt :: IO [EdState] -> IO EdLines
    toLinesExt rsIO = do
         rs@(r:_) <- rsIO -- todo add fallback pattern with (error)
         return $ fromEd r    [" "]

The scanl is definitely evaluated lazy because edLine is getting evaluated for sure (observable by the side effects).

I think it could have to do with concatM:

concatM :: (Foldable t, Monad m) => t (m [a]) -> m [a]
concatM xsIO = foldr (\accIO xIO -> do {x <- xIO; acc <- accIO; return $ acc    x}) (return []) xsIO

CodePudding user response:

All I/O in Haskell is explicitly ordered. The last two lines of your main function desugar into something like

ed (lines xs) >>= (\edLines -> putStr $ unlines edLines)

>>= sequences all of the I/O effects on the left before all of those on the right. You're constructing an I/O action of the form generate line 1 >> ... >> generate line n >> output line 1 >> ... >> output line n.

This isn't really an evaluation order issue, it's a correctness issue. An implementation is free to evaluate in any order it wants, but it can't change the ordering of I/O actions that you specified, any more than it can reorder the elements of a list.

Here's a toy example showing what you need to do:

lineProducingActions :: [IO String]
lineProducingActions = replicate 10 getLine

wrongOrder, correctOrder :: IO ()

wrongOrder = do
  xs <- sequence lineProducingActions
  mapM_ putStrLn xs

correctOrder = do
  let xs = [x >>= putStrLn | x <- lineProducingActions]
  sequence_ xs

Note that you can decouple the producer and consumer while getting the ordering you want. You just need to avoid combining the I/O actions in the producer. I/O actions are pure values that can be manipulated just like any other values. They aren't side-effectful expressions that happen immediately as they're written. They happen, rather, in whatever order you glue them together in.

CodePudding user response:

You would need to use unsafeInterleaveIO to schedule some of your IO actions for later. Beware that the IO actions may then be executed in a different order than you might first expect!

However, I strongly recommend not doing that. Change your IO [String] action to print each line as it's produced instead.

Alternately, if you really want to maintain the computation-as-pipeline view, check out one of the many streaming libraries available on Hackage (streamly, pipes, iteratees, conduit, machines, and probably half a dozen others).

CodePudding user response:

Thanks to @benrg answer I was able to solve the issue with the following code:

ed :: [EdCmdLine] -> [IO EdLines]
ed cmds = map (>>= return . toLines . head) $ edHistIO where
    toLines :: EdState -> EdLines
    toLines r = fromEd r    [" "]

    edHistIO = edRec defHs cmds (return [initState])
    edRec :: [HandleHandler] -> [EdCmdLine] -> IO EdHistory -> [IO EdHistory]
    edRec _ [] hist = [hist]                                -- if CTRL   D
    edRec defHs (cmd:cmds) hist = let next = edLine defHs cmd hist in next : edRec defHs cmds next

main = getContents >>= mapM_ (>>= (putStr . unlines)) . ed . lines
  • Related