Qwertford
Qwertford

Reputation: 1189

First time using the Maybe type in Haskell, is my understanding wrong?

From my understanding, the Maybe type is something you can combine with another type. It lets you specify a condition for the inputs that you combined it with using the "Just... Nothing" format.

an example from my lecture slides is a function in Haskell that gives the square root of an input, but before doing so, checks to see if the input is positive.:

maybe_sqrt :: Maybe Float -> Maybe Float
maybe_sqrt maybe_x = case maybe_x of
    Just x
        | x >= 0 -> Just (sqrt x)
        | otherwise -> Nothing
    Nothing -> Nothing

However, I don't understand why this function uses both cases and guards. Why can't you just use guards, like this?:

maybe_sqrt :: Maybe Float -> Maybe Float
maybe_sqrt x
        | x >= 0 = Just (sqrt x)
        | otherwise = Nothing

Upvotes: 2

Views: 4013

Answers (4)

Alexey Romanov
Alexey Romanov

Reputation: 170713

This is also more an extended comment (if this is your first time working with Maybe, you may not have encountered type classes yet; come back when you do). As others already said, x has type Maybe Float. But writing x >= 0 doesn't require x to be Float. What does it actually require? >= has type (Ord a) => a -> a -> Bool, which means it works for any types which are instances of Ord type class (and Maybe Float is one), and both arguments must have the same type. So 0 must be a Maybe Float as well! Haskell actually allows this, if Maybe Float belongs to the Num type class: which it doesn't in the standard library, but you could define an instance yourself:

instance Num a => Num (Maybe a) where
    fromInteger x = Just (fromInteger x)
    # this means 0 :: Maybe Float is Just 0.0

    negate (Just x) = Just (negate x)
    negate Nothing = Nothing

    Just x + Just y = Just (x + y)
    _ + _ = Nothing
    # or simpler: 
    # negate = fmap negate
    # (+) = liftA2 (+)
    # similar for all remaining two argument functions
    ...

Now x >= 0 is meaningful. sqrt x is not; you'll need instances for Floating and Fractional as well. Of course, Just (sqrt x) will be Maybe (Maybe a), not Maybe a! But just sqrt x will do what you want.

The problem is that it works kind of by coincidence that Nothing >= 0 is False; if you checked x <= 0, Nothing would pass.

Also, it's generally a bad idea to define "orphan instances": i.e. the instance above should really only be defined in the module defining Maybe or in the module defining Num.

Upvotes: 0

leftaroundabout
leftaroundabout

Reputation: 120711

the Maybe type is something you can combine with another type

Maybe is not a type. It's a type constructor, i.e. you can use it to generate a type. For instance, Maybe Float is a type, but it's a different type from Float as such. A Maybe Float can not be used as a Float because, well, maybe it doesn't contain one!

But to calculate the square root, you need a Float. Well, no problem: in the Just case, you can just unwrap it by pattern matching! But pattern matching automatically prevents you from trying to unwrap a Float out of a Nothing value, which, well, doesn't contain a float which you could compare to anything.

Incidentally, this does not mean you to need trace every possible failure by pattern matching, all the way through your code. Luckily, Maybe is a monad. This means, if your function was a Kleisli arrow

maybe_sqrt :: Float -> Maybe Float
maybe_sqrt x
        | x >= 0 = Just (sqrt x)
        | otherwise = Nothing

(which is fine because it does accept a plain float) then you can still use this very easily with a Maybe Float as the argument:

GHCi> maybe_sqrt =<< Just 4
Just 2.0
GHCi> maybe_sqrt =<< Just (-1)
Nothing
GHCi> maybe_sqrt =<< Nothing
Nothing

As discussed in the comments, there is some disagreement on whether we should nevertheless call Maybe type, or merely a type-level entity. As per research by Luis Casillas, it's actually rather Ok to call it a type.
Anyway: my point was that Maybe Float is not “an OR-combination of the Maybe type (giving failure) and the Float type (giving values)”, but a completely new type with the structure of Maybe a and the optionally-contained elements of Float.

Upvotes: 8

dfeuer
dfeuer

Reputation: 48580

This is more an extended comment than an answer. As leftaroundabout indicated, Maybe is an instance of Monad. It's also an instance of Alternative. You can use this fact to implement your function, if you like:

maybe_sqrt :: Maybe Float -> Maybe Float
maybe_sqrt maybe_x = do
  x <- maybe_x
  guard (x >= 0)
  pure (sqrt x)

Upvotes: 3

comingstorm
comingstorm

Reputation: 26087

If your type were maybe_sqrt :: Float -> Maybe Float then that is how you would do it.

As it is, consider: what should your function do if your input is Nothing? Probably, you would want to return Nothing -- but why should your compiler know that?

The whole point of an "option" type like Maybe is that you can't ignore it -- you are required to handle all cases. If you want your Nothing cases to fall through to a Nothing output, Haskell provides a (somewhat) convenient facility for this:

maybe_sqrt x_in = do
    x <- x_in
    if x >= 0 then return sqrt x
              else Nothing

This is the Maybe instance of Monad, and it does what you probably want. Any time you have a Maybe T expression, you can extract only the successful Just case with pattern <- expression. The only thing to remember is that non-Maybe bindings should use let pattern = expression instead.

Upvotes: 3

Related Questions