Reputation: 4609
Working with Maybe
seems really difficult in Haskell. I was able to implement the function I need after many frustrating compile errors, but it's still completely disorganized and I don't know how else can I improve it.
I need to:
... Maybe
s into one, final Maybe ...
a -> b -> IO ()
with Just a
and Just b
or nothing (*)Here is example with IO
part removed. I need a -> b -> IO ()
, not (a,b) -> IO ()
later but I couldn't figure out how to pass both arguments otherwise (I can mapM_
with one argument only).
import Network.URI
type URL = String
type Prefix = String
fubar :: String -> Maybe (Prefix, URL)
fubar url = case parseURI url of
Just u -> (flip (,) $ url)
<$> (fmap ((uriScheme u ++) "//" ++ ) ((uriRegName <$> uriAuthority u)))
_ -> Nothing
Result:
> fubar "https://hackage.haskell.org/package/base-4.9.0.0/docs/src/Data.Foldable.html#mapM"
Just ("https://hackage.haskell.org"
,"https://hackage.haskell.org/package/base-4.9.0.0/docs/src/Data.Foldable.html#mapM"
)
(*) printing what failed to parse wrong would be nice
Upvotes: 1
Views: 782
Reputation: 91837
This is pretty simple written with do
notation:
fubar :: String -> Maybe (Prefix, URL)
fubar url = do
u <- parseURI url
scheme <- uriScheme u
authority <- uriAuthority u
return (scheme ++ "//" ++ uriRegName authority, url)
Monads in general (and Maybe in particular) are all about combining m (m a)
into m a
. Each <-
binding is an alternate syntax for a call to >>=
, the operator responsible for aborting if it sees a Nothing, and otherwise unwrapping the Just for you.
Upvotes: 13
Reputation: 60463
I'm not entirely clear on precisely what you're asking, but I'll do my best to answer the questions you have presented.
To extract multiple nested Maybe
s into a single final Maybe
is taken care of by Maybe
's monad-nature (also applicative-nature). How specifically to do it depends on how they are nested.
Simplest example:
Control.Monad.join :: (Monad m) => m (m a) -> m a
-- thus
Control.Monad.join :: Maybe (Maybe a) -> Maybe a
A tuple:
squishTuple :: (Maybe a, Maybe b) -> Maybe (a,b)
squishTuple (ma, mb) = do -- do in Maybe monad
a <- ma
b <- mb
return (a,b)
-- or
squishTuple (ma, mb) = liftA2 (,) ma mb
A list:
sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)
-- thus
sequenceA :: [Maybe a] -> Maybe [a]
-- (where t = [], f = Maybe)
Other structures can be flattened by composing these and following the types. For example:
flattenComplexThing :: (Maybe a, [Maybe (Maybe b)]) -> Maybe (a, [b])
flattenComplexThing (ma, mbs) = do
a <- ma
bs <- (join . fmap sequenceA . sequenceA) mbs
return (a, bs)
That join . fmap sequenceA . sequenceA
line is a bit complex, and it takes some getting used to to know how to construct things like this. My brain works in a very type-directed way (read the composition right-to-left):
[Maybe (Maybe b)]
|
sequenceA :: [Maybe _] -> Maybe [_]
↓
Maybe [Maybe b]
|
-- sequenceA :: [Maybe b] -> Maybe [b]
-- fmap f makes the function f work "inside" the Maybe, so
fmap sequenceA :: Maybe [Maybe b] -> Maybe (Maybe [b])
↓
Maybe (Maybe [b])
|
join :: Maybe (Maybe _) -> Maybe _
↓
Maybe [b]
As for the second question, how to do an a -> b -> IO ()
when you have Maybe a
and Maybe b
, presuming you don't want to do the action at all in the case that either one is Nothing
, you just do some gymnastics:
conditional :: (a -> IO ()) -> Maybe a -> IO ()
conditional = maybe (return ())
conditional2 :: (a -> b -> IO ()) -> Maybe a -> Maybe b -> IO ()
conditional2 f ma mb = conditional (uncurry f) (liftA2 (,) ma mb)
Again I found conditional2
in a mainly type-directed way in my mind.
It takes some time to develop your type gymnastics, but then it starts to get really fun. To make code like this readable, it's important to use helper functions, e.g. conditional
above, and name them well (which is arguable about conditional
:-). You'll gradually get familiar with the standard library's helpers. There's no magic bullet here, you just have to get used to it -- it's a language. Work with it, strive for clarity, if something is too ugly try your best to make it prettier. And ask more specific questions :-)
Upvotes: 3
Reputation: 120711
First note that you're just stacking multiple fmap
s there, with α <$> (fmap β (γ <$> uriAuthority u))
. This can (functor laws!) be rewritten α . β . γ <$> uriAuthority u
, i.e.
{-# LANGUAGE TupleSections #-}
...
Just u -> (,url) . ((uriScheme u++"//") ++ ) . uriRegName <$> uriAuthority u
It might be better for legibility to actually keep the layers separate, but then you should also give them names as amalloy suggests.
Further, more strongly:
Extract multiple nested
M
into one, finalM
Well, sounds like monads, doesn't it?
fubar url = do
u <- parseURI url
(,url) . ((uriScheme u++"//") ++ ) . uriRegName <$> uriAuthority u
Upvotes: 5