Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

haskell

xmonad

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.

like image 451
lierdakil Avatar asked May 03 '16 09:05

lierdakil


1 Answers

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 .)
like image 187
lierdakil Avatar answered Nov 20 '22 01:11

lierdakil