2
votes

I want to learn to use Haskell the right way.

I still don't fully understand how to use Maybe as a monad inside another monad. http://learnyouahaskell.com/a-fistful-of-monads tells me that I can do wonderful things with Maybe without pattern matching Just and Nothing at every step. Please help me understand how.

I'm writing a Yesod handler, but that doesn't matter in this case. All that matters is that Handler is a monad.

ugly :: Maybe ByteString -> Maybe MyObj
ugly Nothing = Nothing
ugly (Just text) = (decode . fromStrict) text

getHelloWorldR :: Handler Html
getHelloWorldR = do
  myObjText <- lookupSessionBS "myobj"  :: Handler (Maybe ByteString) -- gets serialized MyObj from a session cookie, or Nothing
  myObj <- return $ ugly myObjText :: Handler (Maybe MyObj)

How can I rewrite it to avoid pattern matching on Nothing?

I tried using >>= but it expects strange types of function and returns strange types. I just can't make it work. I looked at MaybeT, but that means that I need to define a function similar to ugly in my example, that returns a MaybeT Handler MyObj. Seems too complicated.

Edit: replaced lookupSession with lookupSessionBS that returns a ByteString.

3
Just define ugly :: ByteString -> MyObj; then fmap ugly :: Maybe ByteString -> Maybe MyObj. - chepner
I can't, because Aeson.decode returns Maybe MyObj. - BloodySue
">>= expects strange types of function and returns strange types" you need to dwell on it until those stop looking strange to you. also check out the >=> Kleisli composition operator. Monad Laws are easier to grasp with it. "Monad axioms: Kleisli composition forms a category". - Will Ness

3 Answers

2
votes

Since based on the type signature decode . encodeUtf8 . fromString is a function Text -> Maybe MyObj, we can here use (>>=) or its flipped counterpart (=<<) :: Monad m => (a -> m b) -> m a -> m b:

ugly :: Maybe Text -> Maybe MyObj
ugly = (=<<) (decode . encodeUtf8 . fromStrict)

or shorter:

ugly :: Maybe Text -> Maybe MyObj
ugly = (decode . encodeUtf8 . fromStrict =<<)
2
votes
getHelloWorldR = do
  myObjText <- lookupSession "myobj"  :: Handler (Maybe Text)
  myObj <- return $ ugly myObjText :: Handler (Maybe MyObj)
  ...

can be rewritten as

getHelloWorldR = do
  myObjText <- lookupSession "myobj"  :: Handler (Maybe Text)
  let myObj = ugly myObjText :: Maybe MyObj
  ...

Then,

ugly :: Maybe Text -> Maybe MyObj
ugly Nothing = Nothing
ugly (Just text) = (decode . encodeUtf8 . fromStrict) text

can be rewritten as

ugly :: Maybe Text -> Maybe MyObj
ugly maytext = maytext >>= decode . encodeUtf8 . fromStrict

Hence,

getHelloWorldR = do
  myObjText <- lookupSession "myobj"  :: Handler (Maybe Text)
  let myObj :: Maybe MyObj
      myObj = myObjTest >>= decode . encodeUtf8 . fromStrict
  ...
0
votes

You don’t even need monads here — you can rewrite ugly using fmap:

ugly = fmap $ decode . fromStrict

At which point you can even inline it into getHelloWorldR:

getHelloWorldR :: Handler Html
getHelloWorldR = do
  myObjText <- lookupSessionBS "myobj"  :: Handler (Maybe ByteString) -- gets serialized MyObj from a session cookie, or Nothing
  myObj <- return $ fmap (decode . fromStrict) myObjText :: Handler (Maybe MyObj)

Or you could even use the <$> operator, which is an infix version of fmap:

getHelloWorldR :: Handler Html
getHelloWorldR = do
  myObjText <- lookupSessionBS "myobj"  :: Handler (Maybe ByteString) -- gets serialized MyObj from a session cookie, or Nothing
  myObj <- return $ (decode . fromStrict) <$> myObjText :: Handler (Maybe MyObj)

And in fact, since you’re running return and then immediately assigning it via <-, it can be replaced with a let:

getHelloWorldR :: Handler Html
getHelloWorldR = do
  myObjText <- lookupSessionBS "myobj"  :: Handler (Maybe ByteString) -- gets serialized MyObj from a session cookie, or Nothing
  let myObj :: Maybe MyObj
      myObj = (decode . fromStrict) <$> myObjText

But I would also like to present a slightly different view on monads to that in your question. Monads aren’t just a tool to remove pattern matching — they’re a very general abstraction over the concept of running things in sequence. For instance:

  • When using Maybe, ‘running’ a computation consists of checking to see whether it’s Nothing, aborting the computation if this happens, otherwise continuing on. (This corresponds to a computation which can fail.)
  • When using a list, ‘running’ a computation consists of splitting the computation into multiple parts, one for each element in the list, then joining those parts back again. (This corresponds to a nondeterministic computation.)
  • When using IO, ‘running’ a computation is executing it on a computer.
  • When using Handler, ‘running’ a computation is interacting with the request sent and/or responding with something.

So you shouldn’t reach for monads every time you want to manipulate a Maybe — on the contrary, it’s often much easier to do this without monads, as I showed above! Monads only become useful when you need to sequence things. In your example, this is the case with Handler — because you need to sequence multiple interactions with the request — but is not useful with Maybe, because you just need to apply a series of operations to a value which happens to be wrapped inside a Maybe.