7

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.

lierdakil
  • 548
  • 2
  • 9
  • Perhaps, safe coercions: https://wiki.haskell.org/GHC/Coercible – chi May 03 '16 at 11:24
  • Is the newtype really necessary ? I don't immediately see why something like `type ((f :: (* -> *) -> * -> *) :. (g :: (* -> *) -> * -> *)) l a = f (g l) a` wouldn't work. You wouldn't need to use `Compose` then. – madidier May 03 '16 at 13:19
  • @chi, I tried that. GHC gets confused by types pretty fast, so it requires explicit type annotations, which kinda defeats the purpose. – lierdakil May 03 '16 at 17:04
  • @lbm, newtype *is* necessary. Type aliases don't allow partial application. Take a look at `modifyLayout` signature. How would I express `CC m Window` in that case? `m` is `( * -> *) -> * -> *`, not `* -> *` and definitely not `*`. – lierdakil May 03 '16 at 17:21
  • It seems the alternative would be to use the `LiberalTypeSynonyms` language extension. It seems to work from my quick experimentations in ghci. – madidier May 04 '16 at 08:14
  • @lbm, with `LiberalTypeSynonyms`, GHC doesn't get the idea that `m` can be arbitrarily nested, so it ends up complaining that it 'couldn't match `l` with `m1 l` expected by the context `LayoutClass l Window => l Window -> m l Window`'. Which is reasonable, since type is actually `LayoutClass l Window => l Window -> m (m1 l) Window`. And it's exactly the same as _without_ this whole dance around the idea of type composition. – lierdakil May 04 '16 at 08:39
  • What about wrapping your final type transformation chain in a dummy `newtype` ? For instance `newtype CompositionWrapper (f :: (* -> *) -> * -> *) l a = CW (f l a)`. I don't have a fully functional Haskell development environment at the moment, so I can't test it myself. – madidier May 04 '16 at 09:37
  • @madidier, one can't even construct `CompositionWrapper (M1 :. M2) l a` if `(:.)` is a type synonym, even with `LiberalTypeSynonyms` (you'll immediately see why if you try to resolve synonyms yourself). If `(:.)` is a newtype, `CompositionWrapper` is entirely pointless. – lierdakil May 04 '16 at 11:14

1 Answers1

2

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 .)
lierdakil
  • 548
  • 2
  • 9