lens
offers holesOf, which is a somewhat more general and powerful version of this hypothetical function:
holesList :: Traversable t
=>
Your existing solution calls runMag
once for every branch in the tree defined by Ap
constructors.
I haven't profiled anything, but as runMag
is itself recursive, this might slow things down in a large tree.
An alternative would be to tie the knot so you're only (in effect) calling runMag
once for the entire tree:
data Mag a b c where
One :: a -> Mag a b b
Pure :: c -> Mag a b c
Ap :: Mag a b (c -> d) -> Mag a b c -> Mag a b d
instance Functor (Mag a b) where
fmap = Ap . Pure
instance Applicative (Mag a b) where
pure = Pure
(<*>) = Ap
holes :: forall t a. Traversable t => t a -> t (a, a -> t a)
holes = \t ->
let m :: Mag a b (t b)
m = traverse One t
in fst $ go id m m
where
go :: (x -> y)
-> Mag a (a, a -> y) z
-> Mag a a x
-> (z, x)
go f (One a) (One _) = ((a, f), a)
go _ (Pure z) (Pure x) = (z, x)
go f (Ap mg mi) (Ap mh mj) =
let ~(g, h) = go (f . ($j)) mg mh
~(i, j) = go (f . h ) mi mj
in (g i, h j)
go _ _ _ = error "only called with same value twice, constructors must match"
I have not managed to find a really beautiful way to do this. That might be because I'm not clever enough, but I suspect it is an inherent limitation of the type of traverse
. But I have found a way that's only a little bit ugly! The key indeed seems to be the extra type argument that Magma
uses, which gives us the freedom to build a framework expecting a certain element type and then fill in the elements later.
data Mag a b t where
Pure :: t -> Mag a b t
Map :: (x -> t) -> Mag a b x -> Mag a b t
Ap :: Mag a b (t -> u) -> Mag a b t -> Mag a b u
One :: a -> Mag a b b
instance Functor (Mag a b) where
fmap = Map
instance Applicative (Mag a b) where
pure = Pure
(<*>) = Ap
-- We only ever call this with id, so the extra generality
-- may be silly.
runMag :: forall a b t. (a -> b) -> Mag a b t -> t
runMag f = go
where
go :: forall u. Mag a b u -> u
go (Pure t) = t
go (One a) = f a
go (Map f x) = f (go x)
go (Ap fs xs) = go fs (go xs)
We recursively descend a value of type Mag x (a, a -> t a) (t (a, a -> t a))
in parallel with one of type Mag a a (t a)
using the latter to produce the a
and a -> t a
values and the former as a framework for building t (a, a -> t)
from those values. x
will actually be a
; it's left polymorphic to make the "type tetris" a little less confusing.
-- Precondition: the arguments should actually be the same;
-- only their types will differ. This justifies the impossibility
-- of non-matching constructors.
smash :: forall a x t u.
Mag x (a, a -> t) u
-> Mag a a t
-> u
smash = go id
where
go :: forall r b.
(r -> t)
-> Mag x (a, a -> t) b
-> Mag a a r
-> b
go f (Pure x) _ = x
go f (One x) (One y) = (y, f)
go f (Map g x) (Map h y) = g (go (f . h) x y)
go f (Ap fs xs) (Ap gs ys) =
(go (f . ($ runMag id ys)) fs gs)
(go (f . runMag id gs) xs ys)
go _ _ _ = error "Impossible!"
We actually produce both Mag
values (of different types!) using a single call to traverse
. These two values will actually be represented by a single structure in memory.
holes :: forall t a. Traversable t => t a -> t (a, a -> t a)
holes t = smash mag mag
where
mag :: Mag a b (t b)
mag = traverse One t
Now we can play with fun values like
holes (Reverse [1..])
where Reverse
is from Data.Functor.Reverse
.
Here is an implementation that is short, total (if you ignore the circularity), doesn't use any intermediate data structures, and is lazy (works on any kind of infinite traversable):
import Control.Applicative
import Data.Traversable
holes :: Traversable t => t a -> t (a, a -> t a)
holes t = flip runKA id $ for t $ \a ->
KA $ \k ->
let f a' = fst <$> k (a', f)
in (a, f)
newtype KA r a = KA { runKA :: (a -> r) -> a }
instance Functor (KA r) where fmap f a = pure f <*> a
instance Applicative (KA r) where
pure a = KA (\_ -> a)
liftA2 f (KA ka) (KA kb) = KA $ \cr ->
let
a = ka ar
b = kb br
ar a' = cr $ f a' b
br b' = cr $ f a b'
in f a b
KA
is a "lazy continuation applicative functor". If we replace it with the standard Cont
monad, we also get a working solution, which is not lazy, however:
import Control.Monad.Cont
import Data.Traversable
holes :: Traversable t => t a -> t (a, a -> t a)
holes t = flip runCont id $ for t $ \a ->
cont $ \k ->
let f a' = fst <$> k (a', f)
in k (a, f)
This doesn't really answer the original question, but it shows another angle. It looks like this question is actually tied rather deeply to a previous question I asked. Suppose that Traversable
had an additional method:
traverse2 :: Biapplicative f
=> (a -> f b c) -> t a -> f (t b) (t c)
Note: This method can actually be implemented legitimately for any concrete Traversable
datatype. For oddities like
newtype T a = T (forall f b. Applicative f => (a -> f b) -> f (T b))
see the illegitimate ways in the answers to the linked question.
With that in place, we can design a type very similar to Roman's, but with a twist from rampion's:
newtype Holes t m x = Holes { runHoles :: (x -> t) -> (m, x) }
instance Bifunctor (Holes t) where
bimap f g xs = Holes $ \xt ->
let
(qf, qv) = runHoles xs (xt . g)
in (f qf, g qv)
instance Biapplicative (Holes t) where
bipure x y = Holes $ \_ -> (x, y)
fs <<*>> xs = Holes $ \xt ->
let
(pf, pv) = runHoles fs (\cd -> xt (cd qv))
(qf, qv) = runHoles xs (\c -> xt (pv c))
in (pf qf, pv qv)
Now everything is dead simple:
holedOne :: a -> Holes (t a) (a, a -> t a) a
holedOne x = Holes $ \xt -> ((x, xt), x)
holed :: Traversable t => t a -> t (a, a -> t a)
holed xs = fst (runHoles (traverse2 holedOne xs) id)