0

I am trying to stack up IO and Maybe monads but either I don't understand monad transformers well enough or this is not possible using transformers. Can some one help me understand this?

f :: String -> Maybe String

main :: IO ()
main = do
  input <- getLine            -- IO String
  output <- f input           -- Maybe String (Can't extract because it is IO do block)
  writeFile "out.txt" output  -- gives error because writeFile expects output :: String

In the above simplified example, I have a function f that returns a Maybe String and I would like to have a neat way of extracting this in the IO do block. I tried

f :: String -> MaybeT IO String

main :: IO ()
main = do
  input <- getLine              -- IO String
  output <- runMaybeT (f input) -- Extracts output :: Maybe String instead of String
  writeFile "out.txt" output    -- gives error because writeFile expects output :: String

which lets me extract the Maybe String out in the second line of do block but I need to extract the string out of that. Is there a way to do this without using case?

Bilentor
  • 486
  • 1
  • 5
  • 13
  • 2
    Why did you reach for monad transformers here? – Carl Apr 27 '19 at 01:09
  • 1
    @Carl I searched for using two types of monads in a do block (which I understand is not possible) and found some threads suggesting use of `lift` and monad transformers. I didn't know much about monad transformers so took this as a learning moment. Still not sure if it's the right thing to use here. – Bilentor Apr 27 '19 at 01:12
  • 3
    So what do you want to happen when `f` returns `Nothing`? – Carl Apr 27 '19 at 01:18
  • I am actually not sure what would be the best way. I could throw an error and not write to the file in which case I would have to use the `case` statement so I guess that kinda answers my question? – Bilentor Apr 27 '19 at 01:22
  • 2
    One detail worth emphasising is that `<-` in a do-block doesn't literally extract the value from the monad. Rather, it merely allows you to work *as if* you could extract it. For instance, if you have `x <- m` in a `Maybe` do-block, you can then use the rest of the do block to specify what will be done with `x` *in case `m` is not `Nothing`*. If `m` turns out to be `Nothing`, and the possibility remains there, there will be no `x` to work with, and the failure will be propagated through `>>=` accordingly. – duplode Apr 27 '19 at 01:37
  • Right, so if `x <- m` in a `Maybe` `do` block is `Nothing`, it kinda "short circuits" the whole thing? – Bilentor Apr 27 '19 at 02:43
  • @Bilentor Yes. If `m` is `Nothing` there, everything else in the block is ignored and the result of the whole thing is `Nothing`. – Carl Apr 27 '19 at 03:05
  • Related: https://stackoverflow.com/questions/32579133/simplest-non-trivial-monad-transformer-example-for-dummies-iomaybe – danidiaz Apr 27 '19 at 08:43

1 Answers1

4

Let's stick for a moment with your first snippet. If f input is a Maybe String, and you want to pass its result to writeFile "out.txt", which takes a String, you need to deal with the possibility of f input being Nothing. You don't have to literally use a case-statement. For instance:

  • maybe from the Prelude is case analysis packaged as a function;

  • fromMaybe from Data.Maybe lets you easily supply a default value, if that makes sense for your use case;

  • traverse_ and for_ from Data.Foldable could be used to silently ignore Nothing-ness:

    for_ (f input) (writeFile "out.txt") -- Does nothing if `f input` is `Nothing`.
    

Still, no matter what you choose to do, it will involve handling Nothing somehow.

As for MaybeT, you don't really want monad transformers here. MaybeT IO is for when you want something like a Maybe computation but in which you can also include IO computations. If f :: String -> Maybe String already does what you want, you don't need to add an underlying IO layer to it.

duplode
  • 33,731
  • 7
  • 79
  • 150
  • Thanks for explaining that. I didn't know about `fromMaybe` which actually makes sense in my problem. – Bilentor Apr 27 '19 at 01:26
  • @Carl Well spotted. I have added that to the answer; thanks. – duplode Apr 27 '19 at 01:43
  • @Carl omg DUH `traverse_` for this. I have always invented my own combinator that does the same thing. Need to remember that `Maybe` is a "collection". Thanks. – luqui Apr 27 '19 at 05:50