2

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
Qwertford
  • 1,039
  • 2
  • 14
  • 25
  • Because `x` is of type `Maybe Float`, and you can't use function `>=` with a `Maybe Float`. You have to _extract_ the `x` value out of the `Maybe`. For your function to work, it needs to have the type `maybe_sqrt :: Float -> Maybe Float` – Vincent Savard Mar 15 '16 at 18:43

4 Answers4

8

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.

Community
  • 1
  • 1
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • What do people who only call things with kind `*` or `#` "types" call things like `Either Int`, `MonadReader Char`, or `'S 'Z`? Are variables that get applied to things still "type variables"? – dfeuer Mar 15 '16 at 19:00
  • 1
    @dfeuer: I once read the Haskell Reports very closely with this question in mind. They don't address the question directly, but they do refer terms like `Either Int` as types. So I say `Maybe :: * -> *` is both a type constructor and a type. – Luis Casillas Mar 15 '16 at 19:42
  • @dfeuer: [Aha, found it!](http://stackoverflow.com/questions/21491831/what-does-functors-fmap-tell-about-types/21493199#21493199) – Luis Casillas Mar 15 '16 at 19:46
  • @LuisCasillas, that looks good to me! But sometimes (as in this answer) people reject non-`*` entities as "types". I'm mildly curious what those people call such entities. – dfeuer Mar 15 '16 at 19:56
  • Typically, something like "type functors". – comingstorm Mar 16 '16 at 01:20
  • 1
    To make the distinction between `*` and `* -> *` (or generally `j -> k`) types, I would probably call the second one a "parametrized type." @comingstorm I'm not sure I've heard "type functor" before. I feel like that has the potential to cause confusion because of the "baggage" that the word "functor" has in Haskell: not all `* -> *` types are covariant or contravariant functors. I believe they are all technically functors of some sort in a categorical sense, but usually when I hear "functor" in this context I think covariant (or possibly contravariant) `Hask` functor. – David Young Mar 16 '16 at 02:43
  • "But to do a comparison like `x>=0`, you need a `Float`" No. You need the type of `x` to belong to `Ord` (which `Maybe Float` does) and to `Num` (which it doesn't (but you can define a reasonable instance)). Of course, if you do define the necessary instances, `x` will be a `Maybe Float` and `Just (sqrt x)` will be a `Maybe (Maybe Float))`. – Alexey Romanov Mar 16 '16 at 10:58
3

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.

comingstorm
  • 25,557
  • 3
  • 43
  • 67
3

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)
dfeuer
  • 48,079
  • 5
  • 63
  • 167
0

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.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Mathematically, what you're constructing here is the [one-point compactification](https://en.wikipedia.org/wiki/Alexandroff_extension) of the real line. Trouble is that (much like the complex numbers) this does not really permit a topology-consistent strict weak ordering, so... I think it's quite good that we do _not_ have both `Num (Maybe n)` and `Ord (Maybe n)`. – leftaroundabout Mar 16 '16 at 11:36
  • Wait... _is_ this ordering “topologically consistent”? – leftaroundabout Mar 16 '16 at 11:47
  • "Mathematically, what you're constructing here is the one-point compactification of the real line." No, because of the definition for `Ord (Maybe a)`, `Nothing` corresponds quite specifically to _negative_ infinity. If I wanted to define the one-point compactification, I would need a `newtype` (or an actual new type) and it certainly wouldn't have `Ord`. "I think it's quite good that we do not have both Num (Maybe n) and Ord (Maybe n)." That's what I meant by the next-to-last paragraph. – Alexey Romanov Mar 16 '16 at 13:33