4

Consider the following functions, taken from the answers to this problem set:

func6 :: Monad f => f Integer -> f (Integer,Integer)
func6 xs = do
    x <- xs
    return $ if x > 0 then (x, 0)
                      else (0, x)

func6' :: Functor f => f Integer -> f (Integer,Integer)
-- slightly unorthodox idiom, with an partially applied fmap
func6' = fmap $ \x -> if x > 0 then (x,0) else (0,x)

-- func7 cannot be implemented without Monad if we care about the precise
-- evaluation and layzness behaviour:
-- > isJust (func7 (Just undefined))
-- *** Exception: Prelude.undefined
--
-- If we care not, then it is equivalent to func6, and there we can. Note that
-- > isJust (func6 (Just undefined))
-- True    
func7 :: Monad f => f Integer -> f (Integer,Integer)
func7 xs = do
    x <- xs
    if x > 0 then return (x, 0)
             else return (0, x)

-- func9 cannot be implemented without Monad: The structure of the computation
-- depends on the result of the first argument.
func9 :: Monad f => f Integer -> f Integer -> f Integer -> f Integer
func9 xs ys zs = xs >>= \x -> if even x then ys else zs

Although I understand the counterexample for func7, I don't understand the given reasoning for why we can implement func7 and func9 using monads only. How do monad/applicative/functor laws fit with the above reasoning?

Shaurya Gupta
  • 297
  • 2
  • 13
  • From where do those comment come? They're not in [the source you linked to](http://www.seas.upenn.edu/~cis194/fall16/hw/08-functor-applicative.html) – Mark Seemann Mar 15 '18 at 14:45
  • @MarkSeemann I've added the link to solution. – Shaurya Gupta Mar 15 '18 at 14:47
  • `func7` is just `func6` implemented with `>>=` instead of `fmap`, and the solution provides an example input where the two give different results. – chepner Mar 15 '18 at 15:10
  • @chepner I understand that example but I was looking for a more concrete explanation. And `func6` has `return` followed by the conditions on `x` while `func7` has conditions followed by `return`. – Shaurya Gupta Mar 15 '18 at 15:41
  • Sorry, I was looking at `func6'`, which is the `Functor` version. But your question would be clearer if you had included the provided counterexample and asked why it *was* a counterexample. – chepner Mar 15 '18 at 15:45
  • @chepner I understand why it is a counterexample. I'm rather looking for a deeper explanation of the reasoning provided in solution. – Shaurya Gupta Mar 15 '18 at 16:01
  • Omitting that counterexample here changes your question considerably. – chepner Mar 15 '18 at 16:02
  • @chepner I've added the counterexample. – Shaurya Gupta Mar 15 '18 at 16:39
  • On a tangential note, with respect to the comment to `func6'`, I don't see what is "unorthodox" about it -- if anything, I find that spelling more natural than the one in `func6`, not to mention that it allows relaxing a possibly unnecessary `Monad` constraint. – duplode Mar 15 '18 at 19:05

2 Answers2

4

I don't think typeclass laws are what you need to be worrying about here; in fact, I think the typeclasses unnnecessarily complicate the exercise, if your purpose is to understand nonstrictness.

Here's a simpler example where everything is monomorphic, and rather than give examples using bottom, we're going to use :sprint in GHCi to watch the extent of the evaluation.

func6

My x6 example here corresponds to func6 in the question.

λ> x6 = Just . bool 'a' 'b' =<< Just True

Initially, nothing has been evaluated.

λ> :sprint x6
x6 = _

Now we evaluate 'isJust x6'.

λ> isJust x6
True

And now we can see that x6 has been partially evaluated. Only to its head, though.

λ> :sprint x6
y = Just _

Why? Because there was no need to know the result of the bool 'a' 'b' part just to determine whether the Maybe was going to be a Just. So it remains an unevaluated thunk.

func7

My x7 example here corresponds to func7 in the question.

λ> x7 = bool (Just 'a') (Just 'b') =<< Just True
x :: Maybe Char

Again, initially nothing is evaluated.

λ> :sprint x7
x = _

And again we'll apply isJust.

λ> isJust x7
True

In this case, the content of the Just did get evaluated (so we say this definition was "more strict" or "not as lazy").

λ> :sprint x7
x = Just 'b'

Why? Because we had to evaluate the bool application before we could tell whether it was going to produce a Just result.

Chris Martin
  • 28,558
  • 6
  • 66
  • 126
1

Chris Martin's answer covers func6 versus func7 very well. (In short, the difference is that, thanks to laziness, func6 @Maybe can decide whether the constructor used for the result should be Just or Nothing without actually having to look at any value within its argument.)

As for func9, what makes Monad necessary is that the function involves using values found in xs to decide on the functorial context of the result. (Synonyms for "functorial context" in this setting include "effects" and, as the solution you quote puts it, "structure of the computation".) For the sake of illustration, consider:

func9 (fmap read getLine) (putStrLn "Even!") (putStrLn "Odd!")

It is useful to compare the types of fmap, (<*>) and (>>=):

(<$>) :: Functor f     =>   (a ->   b) -> (f a -> f b) -- (<$>) = fmap
(<*>) :: Applicative f => f (a ->   b) -> (f a -> f b)
(=<<) :: Monad f       =>   (a -> f b) -> (f a -> f b) -- (=<<) = filp (>>=)

The a -> b function passed to fmap has no information about f, the involved Functor, and so fmap cannot change the effects at all. (<*>) can change the effects, but only by combining the effects of its two arguments -- the a -> b functions that might be found in the f (a -> b) argument have no bearing on that whatsoever. With (>>=), though, the a -> f b function is used precisely to generate effects from values found in the f a argument.

I suggest Difference between Monad and Applicative in Haskell as further reading on what you gain (and lose) when moving between Functor, Applicative and Monad.

duplode
  • 31,361
  • 7
  • 69
  • 130
  • Enlightening. I was thinking about it in terms of code instead effects. – Shaurya Gupta Mar 15 '18 at 22:10
  • 1
    @shauryagupta "I was thinking about it in terms of code instead effects" -- I'm not sure if what follows reflects what you have in mind; in any case, looking at code from a, so to say, higher ground, using e.g. types and laws often helps to make sense of things. Do note that "effect", in the way I'm using it here, has a rather broad meaning -- the fact that `[8,6,7]` has three elements, for instance, can be considered an effect. See [*What exactly does “effectful” mean*](https://stackoverflow.com/q/33386622/2751851) for discussion of this point. – duplode Mar 15 '18 at 23:37
  • 1
    @shauryagupta "Which answer should I accept by the way?" -- There is no right answer to this one :) Ideally, the accepted answer would cover all aspects of the question in sufficient depth. Doubts about which answer to accept often arise when multiple questions are rolled into one, or when each answerer focuses on different parts of the question. It's not a big deal, though it's one of the reasons why it's best not to ask multiple questions at once. (I don't think you did wrong here, though -- given the context, you might have reasonably expected the answers would be related when you posted.) – duplode Mar 15 '18 at 23:50