Is it better to define Functor in terms of Applicative in terms of Monad, or vice versa?

前端 未结 5 474
南笙
南笙 2021-02-02 13:25

This is a general question, not tied to any one piece of code.

Say you have a type T a that can be given an instance of Monad. Since every mona

相关标签:
5条回答
  • 2021-02-02 14:05

    I think you mis-understand how sub-classes work in Haskell. They aren't like OO sub-classes! Instead, a sub-class constraint, like

    class Applicative m => Monad m
    

    says "any type with a canonical Monad structure must also have a canonical Applicative structure". There are two basic reasons why you would place a constraint like that:

    • The sub-class structure induces a super-class structure.
    • The super-class structure is a natural subset of the sub-class structure.

    For example, consider:

    class Vector v where
        (.^) :: Double -> v -> v
        (+^) :: v -> v -> v
        negateV :: v -> v
    
    class Metric a where
        distance :: a -> a -> Double
    
    class (Vector v, Metric v) => Norm v where
        norm :: v -> Double
    

    The first super-class constraint on Norm arises because the concept of a normed space is really weak unless you also assume a vector space structure; the second arises because (given a vector space) a Norm induces a Metric, which you can prove by observing that

    instance Metric V where
        distance v0 v1 = norm (v0 .^ negateV v1)
    

    is a valid Metric instance for any V with a valid Vector instance and a valid norm function. We say that the norm induces a metric. See http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure .

    The Functor and Applicative super-classes on Monad are like Metric, not like Vector: the return and >>= functions from Monad induce Functor and Applicative structures:

    • fmap: can be defined as fmap f a = a >>= return . f, which was liftM in the Haskell 98 standard library.
    • pure: is the same operation as return; the two names is a legacy from when Applicative wasn't a super-class of Monad.
    • <*>: can be defined as af <*> ax = af >>= \ f -> ax >>= \ x -> return (f x), which was liftM2 ($) in the Haskell 98 standard library.
    • join: can be defined as join aa = aa >>= id.

    So it's perfectly sensible, mathematically, to define the Functor and Applicative operations in terms of Monad.

    0 讨论(0)
  • 2021-02-02 14:16

    I tend to write and see written the Functor instance first. Doubly so because if you use the LANGUAGE DeriveFunctor pragma then data Foo a = Foo a deriving ( Functor ) works most of the time.

    The tricky bits are around agreement of instances when your Applicative can be more general than your Monad. For instance, here's an Err data type

    data Err e a = Err [e] | Ok a deriving ( Functor )
    
    instance Applicative (Err e) where
      pure = Ok
      Err es <*> Err es' = Err (es ++ es')
      Err es <*> _       = Err es
      _      <*> Err es  = Err es
      Ok  f  <*> Ok  x   = Ok (f x)
    
    instance Monad (Err e) where
      return = pure
      Err es >>= _ = Err es
      Ok  a  >>= f = f a
    

    Above I defined the instances in Functor-to-Monad order and, taken in isolation, each instance is correct. Unfortunately, the Applicative and Monad instances do not align: ap and (<*>) are observably different as are (>>) and (*>).

    Err "hi" <*>  Err "bye" == Err "hibye"
    Err "hi" `ap` Err "bye" == Err "hi"
    

    For sensibility purposes, especially once the Applicative/Monad Proposal is in everyone's hands, these should align. If you defined instance Applicative (Err e) where { pure = return; (<*>) = ap } then they will align.

    But then, finally, you may be capable of carefully teasing apart the differences in Applicative and Monad so that they behave differently in benign ways---such as having a lazier or more efficient Applicative instance. This actually occurs fairly frequently and I feel the jury is still a little bit out on what "benign" means and under what kinds of "observation" should your instances align. Perhaps some of the most gregarious use of this is in the Haxl project at Facebook where the Applicative instance is more parallelized than the Monad instance, and thus is far more efficient at the cost of some fairly severe "unobserved" side effects.

    In any case, if they differ, document it.

    0 讨论(0)
  • 2021-02-02 14:17

    I often choose a reverse approach as compared to the one in Abrahamson's answer. I manually define only the Monad instance and define the Applicative and Functor in terms of it with the help of already defined functions in the Control.Monad, which renders those instances the same for absolutely any monad, i.e.:

    instance Applicative SomeMonad where
      pure = return
      (<*>) = ap
    
    instance Functore SomeMonad where
      fmap = liftM
    

    While this way the definition of Functor and Applicative is always "brain-free" and very easy to reason about, I must note that this is not the ultimate solution, since there are cases, when the instances can be implemented more efficiently or even provide new features. E.g., the Applicative instance of Concurrently executes things ... concurrently, while the Monad instance can only execute them sequentially due to the nature monads.

    0 讨论(0)
  • 2021-02-02 14:18

    The magic here, that the Haskell uses the Kleisli-tiplet notation of a monad, that is more convenient way, if somebody wants to use monads in imperative programming like tools.

    I asked the same question, and the answer come after a while, if you see the definitions of the Functor, Applicative, Monad in haskell you miss one link, which is the original definition of the monad, which contains only the join operation, that can be found on the HaskellWiki.

    With this point of view you will see how haskell monads are built up functor, applicative functors, monads and Kliesli triplet.

    A rough explanation can be found here: https://github.com/andorp/pearls/blob/master/Monad.hs And other with the same ideas here: http://people.inf.elte.hu/pgj/haskell2/jegyzet/08/Monad.hs

    0 讨论(0)
  • 2021-02-02 14:28

    Functor instances are typically very simple to define, I'd normally do those by hand.

    For Applicative and Monad, it depends. pure and return are usually similarly easy, and it really doesn't matter in which class you put the expanded definition. For bind, it is sometimes benfitial to go the "category way", i.e. define a specialised join' :: (M (M x)) -> M x first and then a>>=b = join' $ fmap b a (which of course wouldn't work if you had defined fmap in terms of >>=). Then it's probably useful to just re-use (>>=) for the Applicative instance.

    Other times, the Applicative instance can be written quite easily or is more efficient than the generic Monad-derived implementation. In that case, you should definitely define <*> separately.

    0 讨论(0)
提交回复
热议问题