I can make a few comments here, hopefully helpful. This reflects my understanding, which in itself may be wrong.
pure unusually named. Usually functions are called a reference to what they produce, but in pure x it is x , which is pure. pure x gives an applicative functor that carries pure x . Of course, "tolerates" is approximate. Example: pure 1 :: ZipList Int is a ZipList that carries a pure Int , 1 value.
<*> , *> and <* are not functions, but methods (this answers your first problem). f in its types is not general (for example, for functions), but specific, as indicated by a specific instance. That's why they are really not just $ , flip const and const . The specialized type f indicates the semantics of the combination. In the usual applicative programming style, combination means application. But with functors there is an additional dimension represented by the type “carrier” f . In fx there is "content", x , but there is also "context", f .
The "applicative functors" style is designed to provide programming of the "applicative style" with effects. Effects are represented by functors, carriers, context providers; "applicative" referring to the normal applicative style of functional use. Writing just fx to signify an application was once a revolutionary idea. There is no need for additional syntax, no operators (funcall fx) , no CALL , none of these additional things - the combination was not an application ... Not so, with effects, it would seem that again there was a need for a special syntax when programming with effects. The slaughtered beast reappeared.
So, Applicative Programming with Effects came about to make the combination an average simple application again - in a special (possibly effective) context, if they really were in that context. Thus, for a :: f (t -> r) and b :: ft (almost equal), the combination a <*> b has the application of the transferred content (or types t -> r and t ), in the given context (type f )
The main difference from monads: monads are non-linear . AT
do x <- a y <- b z <- c return (x,y,z)
return has access to all the variables above. Functions nested:
a >>= (\x -> b >>= (\y -> c >>= (\z -> .... )))
This can be made flat by making the calculation steps returned repackaged, composite data (this concerns your second problem):
a >>= (\x -> b >>= (\y-> return (x,y))) >>= (\(x,y) -> c >>= (\z-> return (x,y,z))) >>= (\(x,y,z) -> ..... )
and this is essentially an applicative style. Therefore, when your combinations create data that covers all the information needed for further combinations, and there is no need for “external variables”, you can use this combination style.
But if your monadic chain has branches depending on the values of such "external" variables (that is, the results of the previous stages of the monadic calculation), you cannot make a linear chain from it. This is essentially monadic.
by way of illustration, the first example in this article shows how a “monadic” function
sequence :: [IO a] → IO [a] sequence [ ] = return [ ] sequence (c : cs) = do x ← c xs ← sequence cs return (x : xs)
can really be encoded in this "flat, linear" style like
sequen :: (Applicative f) => [fa] -> f [a] sequen [] = pure [] sequen (c : cs) = pure (:) <*> c <*> sequen cs
There is no use here for the ability of the monad to enter into previous results.
A great pair note is a combination without an app. This shows that the essence of what applicative functors add to simple functors is the ability to combine. Then the application is reached by the good old fmap . This suggests combinatorial functions as the best possible name.