I\'m trying to answer this stackoverflow question, using uniplate as I suggested, but the only solution I\'ve come up with so far is pretty ugly.
This seems like a f
There isn't a right way to solve this problem with uniplate, but there is a right way to solve this problem with the same mechanism. The uniplate library doesn't support uniplating a data type with kind * -> *
, but we can create another class to accommodate that. Here's a little minimal uniplate library for types of kind * -> *
. It is based on the current git version of Uniplate
that has been changed to use Applicative
instead of Str
.
{-# LANGUAGE RankNTypes #-}
import Control.Applicative
import Control.Monad.Identity
class Uniplate1 f where
uniplate1 :: Applicative m => f a -> (forall b. f b -> m (f b)) -> m (f a)
descend1 :: (forall b. f b -> f b) -> f a -> f a
descend1 f x = runIdentity $ descendM1 (pure . f) x
descendM1 :: Applicative m => (forall b. f b -> m (f b)) -> f a -> m (f a)
descendM1 = flip uniplate1
transform1 :: Uniplate1 f => (forall b. f b -> f b) -> f a -> f a
transform1 f = f . descend1 (transform1 f)
Now we can write a Uniplate1
instance for Expression
:
instance Uniplate1 Expression where
uniplate1 e p = case e of
Add x y -> liftA2 Add (p x) (p y)
Mul x y -> liftA2 Mul (p x) (p y)
Eq x y -> liftA2 Eq (p x) (p y)
And x y -> liftA2 And (p x) (p y)
Or x y -> liftA2 Or (p x) (p y)
If b x y -> pure If <*> p b <*> p x <*> p y
e -> pure e
This instance is very similar to the emap
function I wrote in my answer to the original question, except this instance places each item into an Applicative
Functor
. descend1
simply lifts its argument into Identity
and runIdentity
's the result, making desend1
identical to emap
. Thus transform1
is identical to postmap
from the previous answer.
Now, we can define reduce
in terms of transform1
.
reduce = transform1 step
This is enough to run an example:
"reduce"
If (And (B True) (Or (B False) (B True))) (Add (I 1) (Mul (I 2) (I 3))) (I 0)
I 7