Question

I have this very simple function

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)

where jsonFile is a path to a file on my harddrive (pardon the lack of do-notation, but I found this more clear to work with)

my question is; is there a way for me to ditch the IO part so I can work with the bytestring alone?

I know that you can pattern match on certain monads like Either and Maybe to get their values out, but can you do something similar with IO?

Or voiced differently: is there a way for me to make readJFile return Maybe Response without the IO?

Was it helpful?

Solution

To expand on my comments, here's how you can do it:

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

In the end, you combine everything in one IO action again:

getAndProcess :: IO (Maybe Response)
getAndProcess = do
  b <- getJson
  return (readJFile b)

OTHER TIPS

You never need to "drag a monad" through any functions, unless they all need to actually do IO. Just lift the entire chain into the monad with fmap (or liftM / liftM2 / ...).

For instance,

f1 :: B.ByteString -> K
f2 :: K -> I
f3 :: K -> J
f4 :: I -> J -> M

and your entire thing is supposed to be like

m :: M
m = let k = "f1 getJson"
    in f4 (f2 k) (f3 k)

The you can simply do

m = fmap (\b -> let k = f1 b
                in f4 (f2 k) (f3 k) )
    getJson

Incidentally, this might look nicer with do notation:

m = do
  b <- getJson
  return $ let k = f1 b
           in f4 (f2 k) (f3 k)

Concerning you edit and the question

is there a way for me to make readJFile return Maybe Response without the IO?

No, that can't possibly work, because readJFile does need to do IO. There's no way escaping from the IO monad then, that's the whole point of it! (Well, there is unsafePerformIO as Ricardo says, but this is definitely not a valid application for it.)

If it's the clunkiness of unpacking Maybe values in the IO monad, and the signatures with parens in them, you may want to looks at the MaybeT transformer.

readJFile' :: MaybeT IO Response
readJFile' = do
   b <- liftIO getJson
   case eitherDecode b of
     Left err -> mzero
     Right ps -> return ps

In general, yes, there is a way. Accompanied by a lot of "but", but there is. You're asking for what it's called an unsafe IO operation: System.IO.Unsafe. It's used to write wrappers when calling to external libraries usually, it's not something to resort to in regular Haskell code.

Basically, you can call unsafePerformIO :: IO a -> a which does exactly what you want, it strips out the IO part and gives you back wrapped value of type a. But, if you look at the documentation, there are a number of requirements which you should guarantee yourself to the system, which all end up in the same idea: even though you performed the operation via IO, the answer should be the result of a function, as expected from any other haskell function which does not operate in IO: it should always have the same result without side effects, only based on the input values.

Here, given your code, this is obviously NOT the case, since you're reading from a file. You should just continue working within the IO monad, by calling your readJFile from within another function with result type IO something. Then, you'll be able to read the value within the IO wrapper (being in IO yourself), work on it, and then re-wrap the result in another IO when returning.

No, there is no safe way to get a value out of the IO monad. Instead you should do the work inside the IO monad by applying functions with fmap or bind (>>=). Also you should use decode instead of eitherDecode when you want your result to be in Maybe.

getJson :: IO B.ByteString
getJson = B.readFile jsonFile

parseResponse :: B.ByteString -> Maybe Response
parseResponse = decode

readJFile :: IO (Maybe Response)
readJFile = fmap parseResponse getJSON

You could also use do notation if that is clearer to you:

readJFile :: IO (Maybe Response)
readJFile = do
    bytestring <- getJson
    return $ decode bytestring

Note that you dont even need the parseResponse function since readJFile specifies the type.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top