38

I'm relatively new to Haskell and began to read "Real World Haskell".

I Just stumbled over the type Maybe and have a question about how to receive the actual value from a Just 1 for example.

I have written the following code:

combine a b c = (eliminate a, eliminate b, eliminate c)
                where eliminate (Just a) = a
                      eliminate Nothing = 0

This works fine if I use:

combine (Just 1) Nothing (Just 2)

But if I change, for example, 1 to a String it doesn't work.

I think I know why: because eliminate has to give back one type, which is, in this case, an Int. But how can I change eliminate to deal at least with Strings (or maybe with all kind of types)?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Moe
  • 1,021
  • 1
  • 9
  • 16
  • What should the response for `Nothing` be if you expect a string back? – Anon. Feb 09 '11 at 01:06
  • 9
    You're looking for a token that can be used to signify "no result" for all types... that's exactly what `Nothing` is. There is no total function `Maybe a -> a`, because there's no uniform way to turn a `Nothing` into an `a`. The best you can do is write a function `(Maybe a, Maybe b, Maybe c) -> Maybe (a, b, c)` – Tom Crockett Feb 09 '11 at 01:31
  • possible duplicate of [Operating on a return from a Maybe that contains "Just"](http://stackoverflow.com/questions/3375483/operating-on-a-return-from-a-maybe-that-contains-just) – Thomas M. DuBuisson Feb 09 '11 at 02:42
  • 3
    As I have demonstrated with my ridiculously long answer, this problem is different than the one TomMD linked (in that one, the poster stated that the inputs would never be Nothing, in this, Nothing is explicitly a possibility, and in fact the source of the type problem) – Dan Burton Feb 09 '11 at 05:12
  • Maybe it would help if you told us what you are actually trying to do. You might want the "fromMaybe" function, or you might want to exploit the fact that Maybe is a monad. – Paul Johnson Feb 09 '11 at 16:42
  • @Paul i hadn't something specific in mind. i just stumbled over the Maybe type, which look a bit strange to me. so i tried to write a function to get back the original value. – Moe Feb 09 '11 at 21:55
  • @Dan Right, but my answer in that thread just ran though some common ways you deal with `Maybe a` (standard functions) and didn't focus on `fromJust` or other partials as the only possibility. (Not that I disagree with your answer, teaching typeclasses seems a good fit here). – Thomas M. DuBuisson Feb 11 '11 at 19:20
  • Possible duplicate of [Unwrapping datatypes in Haskell without extraneous code](http://stackoverflow.com/questions/2100069/unwrapping-datatypes-in-haskell-without-extraneous-code) – jcsahnwaldt Reinstate Monica May 04 '17 at 01:01

5 Answers5

50

From the standard Prelude,

maybe :: b -> (a -> b) -> Maybe a -> b
maybe n _ Nothing = n
maybe _ f (Just x) = f x

Given a default value, and a function, apply the function to the value in the Maybe or return the default value.

Your eliminate could be written maybe 0 id, e.g. apply the identity function, or return 0.

From the standard Data.Maybe,

fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x

This is a partial function (does not return a value for every input, as opposed to a total function, which does), but extracts the value when possible.

BrainFRZ
  • 437
  • 3
  • 15
ephemient
  • 198,619
  • 38
  • 280
  • 391
23

[edit from Author, 6 years later] This is a needlessly long answer, and I'm not sure why it was accepted. Use maybe or Data.Maybe.fromMaybe as suggested in the highest upvoted answer. What follows is more of a thought experiment rather than practical advice.

So you're trying to create a function that works for a bunch of different types. This is a good time to make a class. If you've programmed in Java or C++, a class in Haskell is kind of like an interface in those languages.

class Nothingish a where
    nada :: a

This class defines a value nada, which is supposed to be the class's equivalent of Nothing. Now the fun part: making instances of this class!

instance Nothingish (Maybe a) where
    nada = Nothing

For a value of type Maybe a, the Nothing-like value is, well, Nothing! This will be a weird example in a minute. But before then, let's make lists an instance of this class too.

instance Nothingish [a] where
    nada = []

An empty list is kind of like Nothing, right? So for a String (which is a list of Char), it will return the empty string, "".

Numbers are also an easy implementation. You've already indicated that 0 obviously represents "Nothingness" for numbers.

instance (Num a) => Nothingish a where
    nada = 0

This one will actually not work unless you put a special line at the top of your file

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}

Or when you compile it you can set the flags for these language pragmas. Don't worry about them, they're just magic that makes more stuff work.

So now you've got this class and these instances of it...now let's just re-write your function to use them!

eliminate :: (Nothingish a) => Maybe a -> a
eliminate (Just a) = a
eliminate Nothing  = nada

Notice I only changed 0 to nada, and the rest is the same. Let's give it a spin!

ghci> eliminate (Just 2)
2
ghci> eliminate (Just "foo")
"foo"
ghci> eliminate (Just (Just 3))
Just 3
ghci> eliminate (Just Nothing)
Nothing
ghci> :t eliminate
eliminate :: (Nothingish t) => Maybe t -> t
ghci> eliminate Nothing
error! blah blah blah...**Ambiguous type variable**

Looks great for values and stuff. Notice the (Just Nothing) turns into Nothing, see? That was a weird example, a Maybe in a Maybe. Anyways...what about eliminate Nothing? Well, the resultant type is ambiguous. It doesn't know what we are expecting. So we have to tell it what type we want.

ghci> eliminate Nothing :: Int
0

Go ahead and try it out for other types; you'll see it gets nada for each one. So now, when you use this function with your combine function, you get this:

ghci> let combine a b c = (eliminate a, eliminate b, eliminate c)
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
(2,"foo",Just 3)
ghci> combine (Just 2) Nothing (Just 4)
error! blah blah Ambiguous Type blah blah

Notice you still have to indicate what type your "Nothing" is, or indicate what return type you expect.

ghci> combine (Just 2) (Nothing :: Maybe Int) (Just 4)
(2,0,4)
ghci> combine (Just 2) Nothing (Just 4) :: (Int, Int, Int)
(2,0,4)

Or, you could restrict the types that your function allows by putting its type signature explicitly in the source. This makes sense if the logical use of the function would be that it is only used with parameters of the same type.

combine :: (Nothingish a) => Maybe a -> Maybe a -> Maybe a -> (a,a,a)
combine a b c = (eliminate a, eliminate b, eliminate c)

Now it only works if all three Maybe things are the same type. That way, it will infer that the Nothing is the same type as the others.

ghci> combine (Just 2) Nothing (Just 4)
(2,0,4)

No ambiguity, yay! But now it is an error to mix and match, like we did before.

ghci> combine (Just 2) (Just "foo") (Just (Just 3))
error! blah blah  Couldn't match expected type  blah blah
blah blah blah    against inferred type         blah blah

Well, I think that was a sufficiently long and overblown answer. Enjoy.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198
  • Also note: the new function only works for instances of `Nothinglike` types wrapped in a Maybe. But if a type is not `Nothinglike`, then why would you expect this function to produce a value of that type when given the input of `Nothing` in the first place? ;) – Dan Burton Feb 09 '11 at 05:15
  • 1
    Wow thats a lot of new stuff for an newbie ;) i really get some serious doubts if i understand your solution and about learning this language at all, if it's this hard to solve such a little problem. but yeah, i will take my time, do some examples and decide if it suits me ;) (a little question at the end, will it be easier if, eliminate should only work with Strings and Integer, and Nothing should result in "" or 0?) – Moe Feb 09 '11 at 11:28
  • @Moe trouble is, in Haskell it's hard to make a method that works on more than one type, but not on all types. So the trick is to make it work on certain classes of types. Keep reading RWH and all this stuff should become plain to you soon. – Dan Burton Feb 09 '11 at 14:10
  • 7
    This is almost certainly the wrong answer, over-complicated, and *way* to advanced for such a beginner question to boot. Please don't teach newbies that simple things are incredibly hard in Haskell! – Jonathan Cast Aug 29 '16 at 17:11
  • 1
    Dan, if you have the time (and I don't necessarily expect you to), please update your answer with your hard-earned wisdom to be more of a cohesive whole. – Russia Must Remove Putin Jan 15 '18 at 03:05
  • This is good lore, despite the nay-sayer. So we need to be able to handle `Nothing` properly, and creating a class that helps in this process is necessay. – 147pm Jan 09 '22 at 02:39
5

I'm new to Haskell too, so I don't know if this exists in the platform yet (I'm sure it does), but how about a "get or else" function to get a value if it exists, else return a default?

getOrElse::Maybe a -> a -> a
getOrElse (Just v) d = v
getOrElse Nothing d  = d
Roberto
  • 987
  • 1
  • 9
  • 21
  • 8
    `fromMaybe` http://hackage.haskell.org/package/base-4.7.0.1/docs/Data-Maybe.html#v:fromMaybe – raine Oct 29 '14 at 07:59
2

This is the answer I was looking for when I came to this question:

https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html#v:fromJust

...and similarly, for Either:

https://hackage.haskell.org/package/either-unwrap-1.1/docs/Data-Either-Unwrap.html

They provide functions I would have written myself which unwrap the value from its context.

Ben
  • 54,723
  • 49
  • 178
  • 224
1

The eliminate function's type signature is:

eliminate :: Maybe Int -> Int

That's because it returns 0 on Nothing, forcing the compiler to assume that a :: Int in your eliminate function. Hence, the compiler deduces the type signature of the combine function to be:

combine :: Maybe Int -> Maybe Int -> Maybe Int -> (Int, Int, Int)

and that's precisely why it doesn't work when you pass a String to it.

If you wrote it as:

combine a b c = (eliminate a, eliminate b, eliminate c)
                where eliminate (Just a) = a
                      eliminate Nothing = undefined

then it would have worked with String or with any other type. The reason relies on the fact that undefined :: a, which makes eliminate polymorphic and applicable to types other than Int.

Of course, that's not the aim of your code, i.e., to make the combine function total.

Indeed, even if an application of combine to some Nothing arguments would succeed (that's because Haskell is lazy by default), as soon as you try to evaluate the results you would get a runtime error as undefined can't be evaluated to something useful (to put it in simple terms).

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Cristiano Paris
  • 1,786
  • 2
  • 14
  • 21
  • @Christiano thanks for your answer. as i mentioned in my question i already thought, that it might be 0 to force the compiler to assume the return type to be an integer. but that doesn't help me with my problem. how can you write a function in haskell that takes 3 Maybe types, and returns a tupel, where the values are the value of the maybes, or 0 if it's nothing? – Moe Feb 09 '11 at 13:10
  • 1
    You cannot, if you plan to pass values other than Int in the Maybes. I know this may seem frustrating at first, but think about it: would it be useful? Can you imagine a function consuming the results of combine? What signature it may have? – Cristiano Paris Feb 09 '11 at 13:19