I'm reading Graham Hutton's Programming in Haskell and am confused with the flow of thought outlined below.
He uses the example below to motivate the use of monads by showing the shortcomings of applicative functors for a divide operation where the return type is a Maybe
to handle the error case indicating a potential division by zero scenario.
Given:
data Expr = Val Int | Div Expr Expr
safediv :: Int -> Int -> Maybe Int
safediv _ 0 = Nothing
safediv n m = Just (n `div` m)
eval :: Expr -> Maybe Int
eval (Val n) = pure n --type: Just(n)?
eval (Div x y) = pure safediv <*> eval x <*> eval y --type: Maybe(Maybe Int)?
He goes on to explain:
However, this definition is not type correct. In particular, the function
safediv
has typeInt->Int->Maybe Int
, whereas in the above context a function of typeInt->Int->Int
is required.Replacing
pure safediv
by a custom defined function wound not help either, because this function would need to have typeMaybe(Int->Int->Int)
, which does not provide any means to indicate failure when the second integer argument is zero. (X)The conclusion is that the function
eval
does not fit the pattern of effectful programming that is captured by applicative functors. The applicative style restricts restricts us to applying pure functions to effectful arguments:eval
does not fit this pattern because the functionsafediv
that is used to process the resulting values is not a pure function, but may itself fail.
I'm not a Haskell programmer but from the type of eval (Div x y)
it seems be that of Maybe(Maybe Int)
- which can simply be squashed, no? (Something like a flatten
in Scala or join
in Haskell). What really is the issue here?
Irrespective of whether x,y
are Just(s)/Nothing(s)
it seems safediv
will correctly evaluate - the only issue here is the return type which can be transformed appropriately. How exactly does the author go from his argument to this conclusion is what I'm having a hard time understanding.
...applicative style restricts restricts us to applying pure functions to effectful arguments
Also, why does paragraph marked (X)
above make that claim when the problem just seems to be or return type misalignment.
I understand applicatives can be used for more efficiently chaining computations where the results of one don't impact the other - but in this case I'm rather confused as to how/where the failure would happen and if just a simple return type fix would solve the problem:
eval (Div x y) = join(pure safediv <*> eval x <*> eval y)
And does safediv
have to be pure? AFAIK it could also be of type F[Maybe]
or F[Either]
, no? What may I be missing? I can see where he's going but not sure if this is the right example to get there IMHO.
join
. To getjoin
you need a monad. And that's the whole point here: applicatives not enough, you need a monad. - Fyodor Soikinjoin
(on top the other stuff from Applicative Functor), it is a Monad. - Will Ness