I have been banging my head on the wall for several seconds. I have a bunch of types that represent conversions by the base type (more precisely, layout modifiers in XMonad).
In short, these types are of the form (* -> *) -> * -> * .
What I want to do, for reasons that I really donβt want to discuss here, is to take a stack of these transformations and present them as one transformation by the base type (of the form * -> * ).
My first idea was to define a type layout operator
newtype ((f :: (* -> *) -> * -> *) :. (g :: (* -> *) -> * -> *)) la = Compose (f (gl) a)
And that works, for the most part. But given the 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) la , which is not very beautiful and not very annoying.
So the question is, is there a way to automatically apply Compose until I get what I want?
F.ex., now I am doing 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
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 -> ml Window) -> ConfigMonad
Clarification (EDIT):
The whole idea of ββusing type composition is as follows.
Consider two layout modifiers,
m1 :: LayoutClass la => la -> M1 la
and
m2 :: LayoutClass la => la -> M2 la
If I compose these two, I get
m1m2 :: (LayoutClass la, LayoutClass (M2 l) a) => la -> M1 (M2 l) a m1m2 = m1 . m2
We can assume that there exists an instance LayoutClass la => LayoutClass (M2 l) a . At the same time, suppose there are instances for CC M1 Window and CC M2 Window .
If I now try to pass this to modifyLayout , as 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 a typical composition, I can fix it, because the GHC corresponds to M1 :. M2 M1 :. M2 with m in modifyLayout signature and avoids the confusion of whole nesting. Obviously, type synonyms will not have this property.
UPDATE:
After some grumbling, I found a partial solution (not sure why I hadn't thought about this before, but ok)
You can define a type class like this
class S lft | fl -> t where sq :: (la -> fa) -> (la -> tla)
Functional dependency ensures that the compiler can select an instance on its own.
Then it becomes possible to record 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 (xl))) ((m1 :. m2) :. x) where sq = sq . (Compose .) instance S l (m1 (m2 (m3 (xl)))) (((m1 :. m2) :. m3) :. x) where sq = sq . (Compose .) -- etc
This partially answers my question: sq encapsulates the conversion stack if a specific instance is specified there for a given nesting level.
However, these instances seem to trigger the definition of a recursive instance. At the moment, I could not understand exactly how it would look. So any insight is welcome.