Haskell: сокрытие сбоев в ленивом IO

Это нубский вопрос.

Я хотел бы написать функцию, которая обеспечивает ленивый поток изображений, предположительно что-то вроде:

imageStream :: [IO Image]

К сожалению, функция, которая читает изображения, может потерпеть неудачу, поэтому она выглядит так:

readImage :: IO (Maybe Image)

Итак, функция, которую я могу написать, выглядит так:

maybeImageStream :: [IO (Maybe Image)]

Как реализовать функцию, подобную следующей, при этом сохраняя ленивый ввод-вывод?

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

Семантически, когда вы спрашиваете flattenImageStream для следующего изображения, он должен перебрать список и попытаться прочитать каждое изображение. Он делает это до тех пор, пока не найдет загружаемое изображение и не вернет его.

РЕДАКТИРОВАТЬ: Кажется, есть некоторые разногласия в ответах. Некоторые предложили решения, которые используют sequence, но я почти уверен, что проверил это и обнаружил, что это разрушает лень. (Я проверю это снова, чтобы быть уверенным, когда вернусь к своему компьютеру.) Кто-то также предложил использовать unsafeInterleaveIO, Судя по документации для этой функции, она будет работать, но, очевидно, я хочу максимально уважать систему типов.

4 ответа

Решение

Ты можешь использовать ListT от pipes, который обеспечивает более безопасную альтернативу ленивым IO это правильно делает в этом случае.

Способ, которым вы моделируете свой ленивый поток потенциально неудачных изображений:

imageStream :: ListT IO (Maybe Image)

Предполагая, что у вас есть какая-то функция загрузки изображения типа:

loadImage :: FileName -> IO (Maybe Image)

.. тогда способ создания такого потока будет примерно таким:

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

Если вы используете dirstream библиотеку, то вы можете даже лениво просматривать содержимое каталога.

Функция, отфильтровывающая только успешные результаты, будет иметь такой тип:

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

Обратите внимание, что эта функция работает для любой базовой монады, m, Ничего нет IOконкретно об этом. Это также сохраняет лень!

применение flattenImage в imageStream, дает нам что-то типа:

finalStream :: List IO Image
finalStream = flattenImage imageStream

Теперь предположим, что у вас есть функция, которая использует эти изображения, типа:

useImage :: Image -> IO ()

Если вы хотите обработать финал ListT с использованием useImage функция, вы просто пишете:

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

Это тогда лениво потребляет поток изображения.

Конечно, вы также можете поиграть в гольф кода и объединить все это в следующую гораздо более короткую версию:

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

Я также думаю о добавлении fail определение для ListT чтобы вы могли просто написать:

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

Как и предполагалось, вы можете превратить [m a] в m [a], используя последовательность

так что вы получите:

imageStream :: IO [Image]

тогда вы можете использовать cayMaybes из Data.Maybe, чтобы сохранить только значения Just:

catMaybes `liftM` imageStream

Реализация этого в соответствии с запросом, похоже, потребует знания за пределами монады ввода-вывода, было ли значение внутри ввода-вывода Nothingи поскольку IO предназначен для предотвращения "утечки" его значений во внешний чисто функциональный мир (unsafePerformIO несмотря на это), это было бы невозможно. Вместо этого я рекомендую производить IO [Image]: использовать sequence преобразовать [IO (Maybe Image)] в IO [Maybe Image], а затем использовать Data.Maybe.catMaybes в монаде IO (например, с fmap или же liftM) конвертировать в IO [Image]Например:

flattenImageStream = fmap catMaybes $ sequence maybeImageStream

Я не думаю, что любой из этих других ответов делает именно то, что вы хотите. Потому что я уверен catMaybes будет просто пропустить изображение и не пытаться перезагрузить его. Если вы хотите просто попытаться перезагрузить изображение, попробуйте это.

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 

Но то, что вы делаете, довольно странно. Что если у вас неправильный путь к файлу? Что, если изображение просто не может быть загружено? Вы просто попытаетесь загрузить изображение навсегда. Вам, вероятно, придется несколько раз попытаться загрузить изображение, прежде чем оно сдастся.

Другие вопросы по тегам