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
safedivhas typeInt->Int->Maybe Int, whereas in the above context a function of typeInt->Int->Intis required.Replacing
pure safedivby 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
evaldoes 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:evaldoes not fit this pattern because the functionsafedivthat 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.