Hacker News new | ask | show | jobs
by JoshTriplett 4807 days ago
You can also make Maybe an instance of various typeclasses for even nicer syntax:

    instance Num a => Num (Maybe a) where
        (+) = liftM2 (+)
        (-) = liftM2 (-)
        (*) = liftM2 (*)
        abs = liftM abs
        signum = liftM signum
        negate = liftM negate
        fromInteger = Just . fromInteger

    > Just 4 + 2 * Just 6
    Just 16
    > Nothing * 42
    Nothing
Notice how the fromInteger method allows you to freely mix Maybe and non-Maybe numbers.
3 comments

I don't have a whole lot of Haskell experience ... but these "Maybe" unwrapping functions are rare right? Like a null check I'd think that most of the time you make the check when you get the unreliable input or allocation or whatever, and thereafter in the guts of you program you have the certainty that the input has been "checked".
The real trick is that, conceptually, these are not really "unwrapping" functions--instead, they're "propagating" functions. All they do is take Maybe values from deep inside your computation and string them through to the outside.

In practice, this means that you write a decent part of your program using these techniques, which creates a block of code that produces a Maybe value after taking a bunch of Maybe inputs. Then you only use a case statement at the very end, when you need to plug the Maybe value back into normal code.

All these functions are useful for one particular case: you don't know what to do with a Nothing value, so if you see one anywhere, you just pass it on: your final result is Nothing. That pattern just turns out to be very useful.

>That pattern just turns out to be very useful.

Like NaN, it can be hard to track down where things went wrong.

Except contrary to NaN, the type system encodes where it can exist and what its span is.
Right, you can rule out the pieces that are typed as "NaN-free" - hopefully that's a lot.

This is a great reason to avoid huge chunks of code stitched together staying inside Maybe, while still being convenient on the small scale.

But that's precisely why you abstract it; so you can switch to Error in need be...
> Like NaN, it can be hard to track down where things went wrong.

Unlike NaN, you have the freedom to not use it when you don't want it.

So, Either String a <=> Maybe a, but with a nice reason something went wrong.
Not quite. They are not restricted to error handling. You can have methods returning a Maybe something without it being an error (just like there are plenty of valid reasons for returning null instead of throwing an exception). You can also use Maybe in a data structure:

  data Employee = Employee { name : Text, spouse : Maybe Text }
You may also want to use Either to store two possible outcomes of an operation, though I would recommend using your own sum type for clarity:

  data MyOwnEither a b = MyLeft a | MyRight b
If Nothing is ever an error, than you can add code to handle that case. It isn't really different from some method returning an empty list and other functions being basically no-ops afterward.
Depends on the structure of your program. When the Maybe represents the return value of some function that might fail, like a "null" in another language, you'll probably try to eliminate the Nothing case fairly soon after getting it on, and if you have several to eliminate you might use the monad syntax to avoid repeated checks. (User input will commonly use an Either or Error rather than a Maybe, so that you have some error status for what went wrong, but the same principle applies.)

However, sometimes a Maybe represents an inherently "optional" part of your data model, such as "a Foo may have zero or one Bar". In that case, you'll probably hold onto the Maybe until the point where you'd actually read and use that field.

Yes, that's the great thing about Maybe — any code that doesn't need to care about the uncertainty is freed up from worrying about nulls by guaranteeing it won't get one. Most of your functions will usually deal with the unwrapped type, so if you try to pass the Maybe to these functions, the type-checker will say, "Wait, this function isn't expecting a Maybe. You've done something wrong here." So you have to do your "null check" at the point where you get your Maybe, and then you know for certain that the rest of your code won't explode with a NullPointerException or whatever.
They are pretty rare. Very few functions actually want to use nullable-arguments. Another reason why nullable by default is a bad idea.
Yes. This is perhaps the most important point in this thread. Monads and idioms are neat, but for Maybe's in most situations they are not really necessary since there is often a single point where you `case` on a Mabye and that's it.
Lots of things can give a maybe - lookup in a container, for instance - that might be lower down in your code.
Sounds like a bad idea to me. Even though Num doesn't have an explicit contract, we sort of expect it to behave "nicely".

But here we start with a nice ring like Integer and end up with a type that has this weird, extra element that has no inverse with respect to addition, etc.

For all fixed length two's complement integer types the smallest representable number has no additive inverse, too.
Prelude Data.Int> minBound + minBound :: Int16

0

Ohhh yes, of course, it is still a abelian group.
At least in Clojure, it's considered bad form to extend your own protocols (aka instance type classes) to types you don't own. Isn't that also true of Haskell?

Haskell's type classes, unlike ML functors (I think), are "coherent", which means that you can't have scoped or multiple instances for the same type, lest you risk breaking the type system. With that in mind, extending Maybe to Num would mean a reduction the number of type errors caught in other code that uses Maybe and Num near each other.

Defining instances for both someone else's class and someone else's type is considered bad and GHC will warn:

http://stackoverflow.com/questions/3079537/orphaned-instance...

There are occasions where it can be useful to have a module export an orphan instance for compatibility before it makes it into the more appropriate spot in the standard libs.

And you're right you can't have multiple instances for the same type; a newtype wrapper is required.