Есть ли способ развернуть тип из монады ввода-вывода?
Вопрос
У меня есть эта очень простая функция
import qualified Data.ByteString.Lazy as B
getJson :: IO B.ByteString
getJson = B.readFile jsonFile
readJFile :: IO (Maybe Response)
readJFile = parsing >>= (\d ->
case d of
Left err -> return Nothing
Right ps -> return (Just ps))
where parsing = fmap eitherDecode getJson :: IO (Either String Response)
где jsonFile
это путь к файлу на моем жестком диске (прошу прощения за отсутствие обозначения do, но я нашел это более понятным для работы)
мой вопрос заключается в следующем;есть ли для меня способ избавиться от IO
часть, чтобы я мог работать с байтовой строкой в одиночку?
Я знаю, что вы можете сопоставлять шаблоны для определенных монад, таких как Either
и Maybe
чтобы получить их значения, но можете ли вы сделать что-то подобное с IO
?
Или озвученный по - другому:есть ли у меня способ сделать readJFile
Возврат Maybe Response
без ввода-вывода?
Решение
Чтобы подробнее остановиться на моих комментариях, вот как вы можете это сделать:
getJson :: IO B.ByteString
getJson = B.readFile jsonFile -- as before
readJFile :: B.ByteString -> Maybe Response -- look, no IO
readJFile b = case eitherDecode b of
Left err -> Nothing
Right ps -> Just ps
В конце концов, вы снова объединяете все в одно действие ввода-вывода:
getAndProcess :: IO (Maybe Response)
getAndProcess = do
b <- getJson
return (readJFile b)
Другие советы
Вам никогда не нужно "перетаскивать монаду" через какие-либо функции, если только все они на самом деле не должны выполнять ввод-вывод.Просто поднимите всю цепочку в монаду с помощью fmap
(или liftM
/ liftM2
/ ...).
Например,
f1 :: B.ByteString -> K
f2 :: K -> I
f3 :: K -> J
f4 :: I -> J -> M
и все ваше дело должно быть таким
m :: M
m = let k = "f1 getJson"
in f4 (f2 k) (f3 k)
То, что вы можете просто сделать
m = fmap (\b -> let k = f1 b
in f4 (f2 k) (f3 k) )
getJson
Кстати, это могло бы выглядеть приятнее с do
обозначение:
m = do
b <- getJson
return $ let k = f1 b
in f4 (f2 k) (f3 k)
Относительно вашей правки и вопроса
есть ли у меня способ сделать
readJFile
ВозвратMaybe Response
безIO
?
НЕТ, это никак не может сработать, потому что readJFile
действительно нужно сделать ввод-вывод.Нет никакого способа убежать от IO
тогда монада, вот в чем весь смысл!(Ну, есть unsafePerformIO
как говорит Рикардо, но это определенно не является действительным приложением для этого.)
Если дело в неуклюжести распаковки Maybe
значения в IO
монада и подписи с скобками в них, вы можете захотеть взглянуть на MaybeT
трансформатор.
readJFile' :: MaybeT IO Response
readJFile' = do
b <- liftIO getJson
case eitherDecode b of
Left err -> mzero
Right ps -> return ps
В общем, да, способ есть.Сопровождается множеством "но", но есть.Вы спрашиваете, как это называется небезопасная операция ввода-вывода: System.IO.Небезопасный.Обычно он используется для написания оболочек при вызове внешних библиотек, к нему не приходится прибегать в обычном коде Haskell.
В принципе, вы можете позвонить unsafePerformIO :: IO a -> a
который делает именно то, что вы хотите, он удаляет IO
часть и возвращает вам завернутое значение типа a
.Но, если вы посмотрите на документацию, есть ряд требований, которые вы должны сами гарантировать системе, и все они сводятся к одной и той же идее:даже если вы выполнили операцию через IO, ответ должен быть результатом функции, как и ожидалось от любой другой функции haskell, которая не работает в IO
:он всегда должен давать один и тот же результат без побочных эффектов, только на основе входных значений.
Здесь, учитывая ваш код, очевидно , что это НЕ тот случай, поскольку вы читаете из файла.Вы должны просто продолжить работу внутри монады ввода-вывода, вызвав свой readJFile
из другой функции с типом результата IO something
.Затем вы сможете прочитать значение в IO
оболочка (находящаяся в IO
самостоятельно), поработайте над этим, а затем повторно оберните результат в другой IO
при возвращении.
Нет, безопасного способа получить значение из монады ввода-вывода не существует.Вместо этого вы должны выполнять работу внутри монады ввода-вывода, применяя функции с помощью fmap или bind (>>=).Также вам следует использовать decode вместо eitherDecode, если вы хотите, чтобы ваш результат был в Maybe .
getJson :: IO B.ByteString
getJson = B.readFile jsonFile
parseResponse :: B.ByteString -> Maybe Response
parseResponse = decode
readJFile :: IO (Maybe Response)
readJFile = fmap parseResponse getJSON
Вы также могли бы использовать обозначение do, если это вам понятнее:
readJFile :: IO (Maybe Response)
readJFile = do
bytestring <- getJson
return $ decode bytestring
Обратите внимание, что вам даже не нужна функция parseResponse, поскольку readJFile указывает тип.