haskellmonadsalternative-functormonadplus

Why do safe partial functions use Maybe instead of generalizing to any Monad


I know some people consider fail to be a mistake, and I can see why. (It seems like MonadPlus was made to replace it). But as long as it is there, it seems like it would make sense for partial functions to use fail and pure instead of Just and Nothing. As this would allow you to do everything you can currently do and more, so something like safeHead could give you the traditional Just/Nothing or instead you could return [x]/[].

From what I have read about MonadPlus it seems like it would be better than using fail. But I don't know enough about it to say for sure, and it also would probably involve pulling into Prelude, which might be a good idea, but would be a larger change than just using fail.

So I guess my question is why partial functions don't use fail OR MonadPlus, both seem better than using a concrete type.


Solution

  • So I guess my question is why partial functions don't use fail OR MonadPlus, both seem better than using a concrete type.

    Well, I can't speak to the motivations of the folks who wrote them, but I certainly share their preference. What I'd say here is that many of us follow a school of thought where the concrete type like Maybe would be our default choice, because:

    1. It's simple and concrete;
    2. It models the domain perfectly;
    3. It can be generically mapped into any more abstract solution you can think of.

    Types like a -> Maybe b model the concept of a partial function perfectly, because a partial function either returns a result (Just b) or it doesn't (Nothing), and there are no finer distinctions to be made (e.g., there aren't different kinds of "nothingness").

    Point #3 can be illustrated by this function, which generically transforms Maybe a into any instance of Alternative (which is a superclass of MonadPlus):

    import Control.Applicative
    
    fromMaybe :: Alternative f => Maybe a -> f a
    fromMaybe Nothing = empty
    fromMaybe (Just a) = pure a
    

    So going by this philosophy, you write the partial functions in terms of Maybe, and if you need the result to be in some other Alternative instance then you use a fromMaybe function as an adapter.

    This could be however be argued the other way around, where you'd have this:

    safeHead :: Alternative f => [a] -> f a
    safeHead [] = empty
    safeHead (a:_) = pure a
    

    ...with the argument that typing just safeHead xs is shorter than fromMaybe (safeHead xs). To each their own.