More fun with applicative functors

前端 未结 5 631
我在风中等你
我在风中等你 2020-12-28 17:48

Earlier I asked about translating monadic code to use only the applicative functor instance of Parsec. Unfortunately I got several replies which answered the question I lite

5条回答
  •  萌比男神i
    2020-12-28 18:16

    I can make a few remarks here, hopefully helpful. This reflects my understanding which itself might be wrong.

    pure is unusually named. Usually functions are named referring to what they produce, but in pure x it is x that is pure. pure x produces an applicative functor which "carries" the pure x. "Carries" of course is approximate. An example: pure 1 :: ZipList Int is a ZipList, carrying a pure Int value, 1.

    <*>, *>, and <* are not functions, but methods (this answers your first concern). f in their types is not general (like it would be, for functions) but specific, as specified by a specific instance. That's why they are indeed not just $, flip const and const. The specialized type f specifies the semantics of combination. In the usual applicative style programming, combination means application. But with functors, an additional dimension is present, represented by the "carrier" type f. In f x, there is a "contents", x, but there is also a "context", f.

    The "applicative functors" style sought to enable the "applicative style" programming, with effects. Effects being represented by functors, carriers, providers of context; "applicative" referring to the normal applicative style of functional application. Writing just f x to denote application was once a revolutionary idea. There was no need for additional syntax anymore, no (funcall f x), no CALL statements, none of this extra stuff - combination was application... Not so, with effects, seemingly - there was again that need for the special syntax, when programming with effects. The slain beast reappeared again.

    So came the Applicative Programming with Effects to again make the combination mean just application - in the special (perhaps effectful) context, if they were indeed in such context. So for a :: f (t -> r) and b :: f t, the (almost plain) combination a <*> b is an application of carried contents (or types t -> r and t), in a given context (of type f).

    The main distinction from monads is, monads are non-linear. In

    do {  x        <-  a
       ;     y     <-  b x
       ;        z  <-  c x y
       ;               return 
         (x, y, z) }
    

    the computation b x depends on x, and c x y depends on both x and y. The functions are nested:

    a >>= (\x ->  b x  >>= (\y ->  c x y  >>= (\z ->  .... )))
    

    If b and c do not depend on the previous results (x, y), this can be made flat by making the computation stages return repackaged, compound data (this addresses your second concern):

    a  >>= (\x       ->  b  >>= (\y-> return (x,y)))       -- `b  ` sic
       >>= (\(x,y)   ->  c  >>= (\z-> return (x,y,z)))     -- `c  `
       >>= (\(x,y,z) ->  ..... )
    

    and this is essentially an applicative style (b, c are fully known in advance, independent of the value x produced by a, etc.). So when your combinations create data that encompass all the information they need for further combinations, and there's no need for "outer variables" (i.e. all computations are already fully known, independent of any values produced by any of the previous stages), you can use this style of combination.

    But if your monadic chain has branches dependent on values of such "outer" variables (i.e. results of previous stages of monadic computation), then you can't make a linear chain out of it. It is essentially monadic then.


    As an illustration, the first example from that paper shows how the "monadic" function

    sequence :: [IO a] → IO [a]
    sequence [ ] = return [ ]
    sequence (c : cs) = do
      {  x       <-  c
      ;      xs  <-  sequence cs  -- `sequence cs` fully known, independent of `x`
      ;              return 
        (x : xs) }
    

    can actually be coded in this "flat, linear" style as

    sequence :: (Applicative f) => [f a] -> f [a]
    sequence []       = pure []
    sequence (c : cs) = pure (:) <*> c <*> sequence cs
                      --     (:)     x     xs
    

    There's no use here for the monad's ability to branch on previous results.


    a note on the excellent Petr Pudlák's answer: in my "terminology" here, his pair is combination without application. It shows that the essence of what the Applictive Functors add to plain Functors, is the ability to combine. Application is then achieved by the good old fmap. This suggests combinatory functors as perhaps a better name (update: in fact, "Monoidal Functors" is the name).

提交回复
热议问题