<*>; or, in which I record what I've learned about applicative functors so I don't have to re-derive it later
2025-12-08T02-05/2025-08-12T05:52-05
Ams
TODO: add sources; see browser-session-mark "applicative research"
Prelude
Code blocks have alt text containing prose explanations of each block; if these can be improved upon, please let me know. In particular, I wasn't sure how to balance these explanations between type-theory, using unary functions with currying, and practical use; e.g. map is commonly used with a function and an instance of a functor at once. If I have erred, I've erred towards concision.
In prose, the terms "map" and "apply" are used; these are as defined for functors and applicative functors respectively. "Map" is used in contrast with Haskell's definition of "map", which is specific to lists; "apply" is used rather than "ap" or its symbolic definition for ease of reading. (Or, more precisely, because terseness has its place in formulas, but prose is a different context with different priorities.)
<$> :: Functor f => (a -> b) -> f a -> f b <*> :: Applicative f => f (a -> b) -> f a -> f b
Both map and apply are left-associative with precedence four. Apply is a generalization of map to n-ary functions, in the context of the applicative.
map (+1) Just 5 == Just 6 (+) <$> Just 5 <*> Just 6 == Just 11
The main idea
I have little experience with Haskell, but it's probably possible to achieve the same result as the above application of addition using pattern matching. Application is more elegant, though, and it only took a little further to understand it enough that I felt comfortable using it.
We noted above that map and apply are left-associative with precedence 4, and that apply generalizes map over arity. Another way to look at apply, as implied by its type signature, is that it applies a function within an applicative to a value in that applicative. The above expression maps addition over Just 5; as partial application, this results in a function from numeral to numeral, within the context of a Maybe. This function is then applied to Just 6, resulting in Just 11.
On liftA2
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c liftA2 (+) (Just 5) (Just 6) == (+) <$> Just 5 <*> Just 6 == Just 11
LiftA2 is an abstraction of map and apply for binary functions; it lifts a binary function into an applicative and applies it, hence the name. It's defined in Haskell because it can be more efficient than use of map and apply.
Further questions
<*> :: Applicative f => f (a -> b) -> f a -> f b pure :: Applicative f => a -> f a
Map is, in theory, equivalent to some combination of pure and apply, but I can't figure it out. [For unary functions, map == apply . pure; not sure how to elegantly and concisely generalize.]
Another use of application is to find, given two lists and a function, the Cartesian product of the lists with respect to the function. Since lists are a monad representing nondeterminism, I'm sure you could do something interesting (and/or useful) with this, and I'd be interested to learn what.