I've been banging my head against the wall for a while now. I have a bunch of types, which represent transformations over a base type (more specifically, layout modifiers in XMonad).
Long story short, those types all have kind (* -> *) -> * -> *
.
What I want to do, for reasons I don't really want to discuss here, is take a stack of these transformations, and represent those as a single transformation over base type (of kind * -> *
).
My first idea was to define a type composition operator
newtype ((f :: (* -> *) -> * -> *) :. (g :: (* -> *) -> * -> *)) l a
= Compose (f (g l) a)
And it works, for the most part. But, given a value, say v :: f1 (f2 (f3 (... (fn l))) a
, I have to apply Compose
n-1
times to it to get v' :: (f1 :. f2 :. ... :. fn) l a
, which is not very pretty and kind of annoying.
So, question is, is there a way to automagically apply Compose
until I get what I want?
F.ex., now I do something like this:
modifyLayout $ Compose . Compose . Compose . Mirror . avoidStruts . minimize . smartBorders
What I want to do:
modifyLayout' $ Mirror . avoidStruts . minimize . smartBorders
where modifyLayout' = modifyLayout . magicCompose
A related question: maybe there is a better way to express the same concept?
For reference, modifyLayout
is
modifyLayout :: (CC m Window)
=> (forall l. (LayoutClass l Window) => l Window -> m l Window)
-> ConfigMonad
Clarification (EDIT):
The whole idea behind using type composition is this.
Consider two layout modifiers,
m1 :: LayoutClass l a => l a -> M1 l a
and
m2 :: LayoutClass l a => l a -> M2 l a
If I compose these two, I get
m1m2 :: (LayoutClass l a, LayoutClass (M2 l) a) => l a -> M1 (M2 l) a
m1m2 = m1 . m2
We can assume there is an instance LayoutClass l a => LayoutClass (M2 l) a
. While at it, also assume there are instances for CC M1 Window
and CC M2 Window
.
If I now try to feed this into modifyLayout
defined above:
modifyLayout m1m2
GHC immediately gets confused by nested types and complains:
Couldn't match type ‘l’ with ‘M2 l’
‘l’ is a rigid type variable bound by
a type expected by the context:
LayoutClass l Window => l Window -> M1 l Window
Expected type: l Window -> M1 l Window
Actual type: l Window -> M1 (M2 l) Window
Using type composition, I can rectify that, since GHC matches M1 :. M2
with m
in modifyLayout
signature, and avoids whole nesting confusion. Type synonyms obviously won't have this property.
UPDATE:
After some poking, I found a partial solution (not sure why I didn't think of it sooner, but oh well)
It's possible to define a typeclass like this
class S l f t | f l -> t where
sq :: (l a -> f a) -> (l a -> t l a)
Functional dependency ensures that compiler will be able to select instance by itself.
Then, it becomes possible to write instances like this
instance S l (m1 l) m1 where
sq = id
instance S l (m1 (m2 l)) (m1 :. m2) where
sq = sq . (Compose .)
instance S l (m1 (m2 (x l))) ((m1 :. m2) :. x) where
sq = sq . (Compose .)
instance S l (m1 (m2 (m3 (x l)))) (((m1 :. m2) :. m3) :. x) where
sq = sq . (Compose .)
-- etc
This partially answers my question: sq
encapsulates a transformation stack, provided there's an instance defined for a given level of nesting.
However, these instances seem to beg for recursive instance definition. As of yet, I wasn't able to figure out how exactly that would look. So, any insight is welcome.
Thanks to Adam Vogt (@aavogt), I was able to finally reach a satisfying conclusion.
I was on the right track with instances of S
class. It turns out, reversing instance dependency allows typechecker to infer other instances. However, IncoherentInstances
extension is required for recursion termination (i.e. base case).
Here's the code:
instance {-# INCOHERENT #-} S l (m l) m where
sq = id
instance S l ((f :. g) l') t => S l (f (g l')) t where
sq = squash . (Compose .)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With