开发者

Lazily evaluate monadic functions in Haskell

开发者 https://www.devze.com 2023-03-05 02:26 出处:网络
I can\'t seem to figure out a workaround for this issue i\'m having. I have something like this: getFilePathForDay :: Day -> IO (Maybe FilePath)

I can't seem to figure out a workaround for this issue i'm having.

I have something like this:

  getFilePathForDay :: Day -> IO (Maybe FilePath)

  getFilePathForDays date days = do
      files <- mapM getFilePathForDay $ iterate (addDays 1) date
      return . take days . catMaybes $ files

I am trying to get x amount of valid file paths where x is the number of days I want, but the above code just runs forever. I have ran into this problem before with monads and I was wondering if there is a workar开发者_运维百科ound available for this issue I am having.

Thanks!


The code runs forever because you are trying to sequence an infinite number of side-effecting computations in your call to mapM.

Since you don't know ahead of time how many of these computations you will need to run (as indicated by your use of catMaybes), you will have to interleave the calls to getFilePathForDay with the rest of your computation. This could be done using unsafeInterleaveIO, but as the name suggests, this is not a recommended approach.

You could implement this manually as:

getFilePathForDays _ 0 = return []
getFilePathForDays date days = do
    mpath <- getFilePathForDay date
    case mpath of
        Just path -> (path :) <$> remaining (days-1)
        Nothing   -> remaining days
  where
    remaining days = getFilePathForDays (addDays 1 date) days

There is probably a more elegant way, but it's not coming to me right now :)


Sometimes lazy IO is the right approach. Since you have an infinite, lazy sequence of days, and you're sampling, lazily, some (dynamic) range from that sequence, we should also hit the filesystem only on demand.

This can be done with unsafeInterleaveIO:

import System.IO.Unsafe         ( unsafeInterleaveIO )

-- return a lazy list of filepaths, pull as many as you need
getFilePathForDays :: Day -> Int -> IO [FilePath]
getFilePathForDays date days = do

    let go (d:ds) = unsafeInterleaveIO $ do
                        f  <- getFilePathForDay d
                        fs <- go ds
                        return (f:fs)

    -- an infinite, lazy stream of filepaths
    fs <- go (iterate (addDays 1) date)

    return . take days . catMaybes $ fs

Where go returns a lazy stream of results, and we take as many as we need. Easy.

0

精彩评论

暂无评论...
验证码 换一张
取 消