Applicative函子能否实现类似fmap.fmap的双层操作?
<***> Great question! This is a common pattern when working with nested Applicatives, and it's totally possible to replicate the "deep application" behavior we get with fmap.fmap for functors. Let's break this down step by step.
Why (<*>).(<*>) Isn't What We Want
First, let's clarify why composing <*> directly doesn't work. The type signature you provided for (<*>).(<*>) makes sense when you remember how function composition interacts with the Applicative instance for functions (where <*> acts like the S combinator: (f <*> g) x = f x (g x)). But for nested Applicatives (like f (g a)), this composition doesn't align with our goal of applying a function wrapped in two layers to a value wrapped in two layers.
The Solution: Use liftA2 or Manual Applicative Composition
The key insight here is that we need to:
- Apply the inner Applicative's
<*>to combineg (a->b)andg aintog b - Lift that inner application into the outer Applicative context
We can do this neatly with liftA2 from Control.Applicative, which lifts a binary function into an Applicative context. Here's how to define your <***> function:
import Control.Applicative -- Type signature matches exactly what you're asking for (<***>) :: (Applicative f, Applicative g) => f (g (a -> b)) -> f (g a) -> f (g b) (<***>) = liftA2 (<*>)
If you prefer to write it explicitly without liftA2, it's equivalent to:
(<***>) f ga = (<*>) <$> f <*> ga
Let's break down this explicit version:
(<*>) <$> ftakes ourf (g (a->b))and maps the inner<*>(which has typeg (a->b) -> g a -> g b) over the outer Applicative, resulting inf (g a -> g b)- We then use
<*>to apply that wrapped function to ourf (g a), giving usf (g b)exactly as desired.
Example Usage
Let's test this with the example you mentioned: combining Just (+5) and [1,2,3]. We need to wrap each in the outer Applicative context first:
-- Outer Applicative is [], inner is Maybe pure (Just (+5)) <***> fmap pure [1,2,3] -- Result: [Just 6, Just 7, Just 8]
Another example with nested Maybe and []:
-- Function wrapped in Maybe []: Just [(+1), (*2)] -- Value wrapped in Maybe []: Just [1,2,3] Just [(+1), (*2)] <***> Just [1,2,3] -- Result: Just [2,3,4,2,4,6]
And mixing failure cases in the inner Applicative:
[Just (+5), Nothing] <***> [Just 1, Nothing, Just 3] -- Result: [Just 6, Nothing, Just 8, Nothing, Nothing, Nothing]
This pattern generalizes to any two Applicative instances, just like fmap.fmap works for any two Functors.
内容的提问来源于stack exchange,提问作者hgiesel

