Reputation: 1189
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
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
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
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
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