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
Но то, что вы делаете, довольно странно. Что если у вас неправильный путь к файлу? Что, если изображение просто не может быть загружено? Вы просто попытаетесь загрузить изображение навсегда. Вам, вероятно, придется несколько раз попытаться загрузить изображение, прежде чем оно сдастся.