Haskell: Hiding glitches in lazy IO

This is a question about noob.

I would like to write a function that provides a lazy stream of images, presumably something like:

imageStream :: [IO Image] 

Unfortunately, a function that reads images may fail, so it looks like this:

 readImage :: IO (Maybe Image) 

So, the function I can write looks like this:

 maybeImageStream :: [IO (Maybe Image)] 

How to implement such a function, as, for example, when saving lazy I / O?

 flattenImageStream :: [IO (Maybe Image)] -> [IO Image] 

Semantically, when you ask flattenImageStream for the next image, it should flattenImageStream over the list and try to read each image. He does this until he finds an image that loads and returns it.

EDIT: There are apparently some disagreements in the answers. Some of them have suggested solutions that use sequence , but I'm sure I experienced this and found that it eradicates laziness. (I will check it again to make sure when I get back to my computer.) Someone also suggested using unsafeInterleaveIO . From the documentation for this function, this seems to work, but obviously I want to respect the type system as much as possible.

+6
source share
4 answers

You can use ListT from pipes , which provides a safer alternative to lazy IO , which does the right thing in this case.

How do you model your lazy stream of potentially bad images:

 imageStream :: ListT IO (Maybe Image) 

Assuming you have an image loading function like:

 loadImage :: FileName -> IO (Maybe Image) 

.. the way you build such a stream will be something like:

 imageStream = do fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"] lift $ loadImage fileName 

If you use the dirstream library , you can even lazily flow through the contents of the directory.

A function that filters out only successful results will be of the following type:

 flattenImageStream :: (Monad m) => ListT m (Maybe a) -> ListT ma flattenImageStream stream = do ma <- stream case ma of Just a -> return a Nothing -> mzero 

Note that this function works for any base monad, m . There is nothing IO . He also keeps laziness!

Applying flattenImage to an imageStream gives us something like:

 finalStream :: List IO Image finalStream = flattenImage imageStream 

Now let's say that you have a function that consumes these images, such as:

 useImage :: Image -> IO () 

If you want to process the final ListT using the useImage function, you simply write:

 main = runEffect $ for (every finalStream) $ \image -> do lift $ useImage image 

This will lazily consume the image stream.

Of course, you can also play golf code and combine all this into the following much shorter version:

 main = runEffect $ for (every image) (lift . useImage) where image = do fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"] maybeImage <- lift $ loadImage fileName case maybeImage of Just img -> return img Nothing -> mzero 

I am also thinking of adding a fail definition for ListT so you can simply write:

 main = runEffect $ for (every image) (lift . useImage) where image = do fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"] Just img <- lift $ loadImage fileName return img 
+9
source

u was supposed to turn [ma] into m [a] using the sequence

so you get:

 imageStream :: IO [Image] 

then you can use cayMaybes from Data.Maybe to save only Just values:

 catMaybes `liftM` imageStream 
+1
source

Implementing this on demand seems to require knowledge outside the IO coin, regardless of whether the value was inside the IO Nothing , and since the IO is designed to prevent it from β€œleaking” to the purely functional world ( unsafePerformIO despite this), this would not be possible . Instead, I recommend creating an IO [Image] : use sequence to convert [IO (Maybe Image)] to IO [Maybe Image] , and then use Data.Maybe.catMaybes in the IO monad (for example, with fmap or liftM ) to convert in IO [Image] , for example:

 flattenImageStream = fmap catMaybes $ sequence maybeImageStream 
0
source

I do not think that any of these other answers do exactly what you want. Because I'm sure catMaybes just skip the image and not try to reload it. If you just want to try reloading the image, try this.

 flattenImageStream :: [IO (Maybe Image)] -> IO [Image] flattenImageStream xs = mapM untilSuc xs untilSuc :: IO (Maybe a) -> IO a untilSuc f = do res <- f case res of Nothing -> untilSuc f Just i -> return i 

But what you do is weird. What to do if you have the wrong file path? What if the image is simply impossible to upload? You just try to download the image forever. You may have to try downloading the image several times before submitting it.

0
source

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


All Articles