Normalize applicative expressions
by simplifying intermediate pure and (<$>) and reassociating (<*>).
This works by transforming the underlying applicative functor into one whose
operations (pure, (<$>), (<*>)) reassociate themselves by inlining
and beta-reduction.
It relies entirely on GHC's simplifier. No rewrite rules, no Template
Haskell, no plugins.
Example
In the following traversal, one of the actions is pure b, which
can be simplified in principle, but only assuming the applicative functor
laws. As far as GHC is concerned, pure, (<$>), and (<*>) are
completely opaque because f is abstract, so it cannot simplify this
expression.
data Example a = Example a Bool [a] (Example a)
traverseE :: Applicative f => (a -> f b) -> Example a -> f (Example b)
traverseE go (Example a b c d) =
Example
<$> go a
<*> pure b
<*> traverse go c
<*> traverseE go d
-- 1 <$>, 3 <*>
Using this library, we can compose actions in a specialized applicative
functor Aps f, keeping the code in roughly the same structure.
In the following snippet, identifiers exported by the library are highlighted.
traverseE :: Applicative f => (a -> f b) -> Example a -> f (Example b)
traverseE go (Example a b c d) =
Example
<$>^ go a
<*> pure b
<*>^ traverse go c
<*>^ traverseE go d
&lowerAps
-- 1 <$>, 3 <*>
GHC simplifies that traversal to the following, using only two
combinators in total.
traverseE :: Applicative f => (a -> f b) -> Example a -> f (Example b)
traverseE go (Example a b c d) =
liftA2 (\a' -> Example a' b)
(go a)
(traverse go c)
<*> traverseE go d
-- 1 liftA2, 1 <*>
The following example with a tree-shaped structure also reduces to the same
list-shaped expression above.
traverseE :: Applicative f => (a -> f b) -> Example a -> f (Example b)
traverseE go (Example a b c d) =
(\((a', b'), (c', d')) -> Example a' b' c' d')
<$> ((,) <$> ((,) <$>^ go a
<*> pure b)
<*> ((,) <$>^ traverse go c
<*>^ traverseE go d))
&lowerAps
-- 4 <$>, 3 <*>
Such structure occurs when using an intermediate definition (which itself
uses the applicative operators) as the right operand of (<$>) or
(<*>).
This could also be found in a naive generic implementation of traverse
using GHC.Generics.
Usage
The main idea is to compose applicative actions not directly in your applicative
functor f, but in a transformed one Aps f.
An applicative functor transformer which accumulates f-actions (things of type f x)
in a normal form.
It constructs a value of type f a with the following syntactic invariant.
It depends on the number of f-actions a1 ... an composing it,
which are delimited using liftAps:
Zero action: pure x
One action: f <$> a1
Two or more actions: liftA2 f a1 a2 <*> a3 <*> ... <*> an
Instances2Functor, Applicative
Functor (Apsf)Defined in ap-normalize-0.1.0.1 · ApNormalize.Aps
& is a reverse application operator. This provides notational
convenience. Its precedence is one higher than that of the forward
application operator $, which allows & to be nested in $.
This is a version of flipid, where id is specialized from a -> a to (a -> b) -> (a -> b)
which by the associativity of (->) is (a -> b) -> a -> b.
flipping this yields a -> (a -> b) -> b which is the type signature of &
Examples
Example1 expression
>>> 5 & (+1) & show"6"
Example1 expression
>>> sqrt $ [1 / n^2 | n <- [1..1000]] & sum & (*6)3.1406380562059946