TransWikia.com

Is IO an instance of Functor just because it is a Monad in the first place?

Stack Overflow Asked on December 2, 2021

From LYAH I understand that the do notation is just syntactic sugar for monadic style; and from the wikibook I read more or less the same; so my understanding is that there can’t be any do notation if there’s no Monad instance.

Yet I read this definition of the Functor instance of the IO type ctor.

instance Functor IO where  
    fmap f action = do  
        result <- action  
        return (f result)  

which is just syntactic sugar for the following, isn’t it?

instance Functor IO where  
    fmap f action = action >>= return . f  

which implies the underling assumption the IO is instance of Monad first; and this come against that fact that every Monad is a Functor and not the other way around.

In fact, I had absorbed that a Monad is "something more than" an Applicative, which is in turn "something more than" a Functor, which goes together with Applicative‘s definition enforcing the Functor constraint on its instances (and Monad‘s definition ideally requiring that its instances are Applicatives, as in don’t make it a Monad if it’s not an Applicative).

In other words, the code above makes me think that there would be no way to write the Functor instance for IO, if IO was not a Monad in the first place.

And now that I think about it, maybe this is just like saying that IO was created as a full-fledged Monad, and the instance above was later made just for completeness and mathematical consistency.

But I’m confused, and so I seek for help here.

One Answer

A type having an instance of Monad implies it must have a definition of Functor (and Applicative), but that doesn’t mean the Functor instance must be defined “first”, only that both instances must exist. As long as the method implementations aren’t defined in terms of each other circularly, there’s no problem.

In fact, it often makes sense to implement Monad for a type first, then define Applicative and Functor mechanically in terms of Monad operations, because Monad is more powerful, so the other instances are just restrictions of the Monad instance:

-- These work for any T with a Monad instance.

instance Functor T where
  fmap f x = do
    x' <- x
    return (f x')

instance Applicative T where
  pure = return
  f <*> x = do
    f' <- f
    x' <- x
    return (f' x')

This is precisely because “a Monad is ‘something more than’ an Applicative, which is in turn ‘something more than’ a Functor”.

It’s also worth noting that originally, the Monad and Functor classes were unrelated, and the Applicative class (added later) was as well. There were separate equivalent functions for each, like fmap, liftA, and liftM; pure and return; (<*>) and ap; traverse and mapM. Conventionally you would write a Monad instance and implement the other classes in terms of that. These separate definitions still exist, but are now redundant: since the “Applicative–Monad Proposal” made Applicative a superclass of Monad, and Functor of Applicative, you can always use e.g. pure instead of return or traverse instead of mapM, since they’re the same functions, but work in strictly more contexts. So there’s also some historical context as to why there might be separate definitions of these instances for a type.

As @dfeuer points out, there are some data structures where mapM (mapM_, forM, forM_) is more efficient than traverse (resp. traverse_, for, for_), such as Data.Vector types. In the specific case of vectors, I believe this is because the library can take advantage of monadic sequencing as an optimisation, to enable more streaming and allocating results in place. But they are equivalent, in that traverse should always produce the same result.

Answered by Jon Purdy on December 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP