Is there a way to easily encapsulate a stack of types '(f1 (f2 (f3 .... fn t))) a' as 'F t a'?

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.

+7
haskell xmonad
source share
1 answer

Thanks to Adam Vogt (@aavogt), I was finally able to reach a satisfactory conclusion.

I was on the right track with instances of class S It turns out that the inverse instance dependency allows typechecker to output other instances. However, the expansion of IncoherentInstances is required to complete the recursion (i.e., the Base case).

Here is the code:

 instance {-# INCOHERENT #-} S l (ml) m where sq = id instance S l ((f :. g) l') t => S l (f (g l')) t where sq = squash . (Compose .) 
+2
source share

All Articles