2016-05-03 9 views
7

Sto sbattendo la testa contro il muro da un po 'di tempo. Ho un sacco di tipi, che rappresentano le trasformazioni su un tipo di base (più specificamente, modificatori di layout in XMonad).C'è un modo per incapsulare facilmente una pila di tipi? (F1 (f2 (f3 .... fn t))) a 'come' F t a '?

Per farla breve, questi tipi hanno tutti tipo (* -> *) -> * -> *.

Quello che voglio fare, per ragioni che non voglio discutere qui, è prendere una pila di queste trasformazioni e rappresentarle come una singola trasformazione rispetto al tipo di base (di tipo * -> *).

La mia prima idea era quella di definire un operatore di tipo composizione

newtype ((f :: (* -> *) -> * -> *) :. (g :: (* -> *) -> * -> *)) l a 
     = Compose (f (g l) a) 

e funziona, per la maggior parte. Ma, dato un valore, diciamo v :: f1 (f2 (f3 (... (fn l))) a, devo applicare Composen-1 volte ad esso per ottenere v' :: (f1 :. f2 :. ... :. fn) l a, che non è molto carino e un po 'fastidioso.

Quindi, la domanda è, c'è un modo per applicare automagicamente Compose finché non ottengo quello che voglio?

p.es., ora faccio qualcosa di simile:

modifyLayout $ Compose . Compose . Compose . Mirror . avoidStruts . minimize . smartBorders 

Quello che voglio fare:

modifyLayout' $ Mirror . avoidStruts . minimize . smartBorders 
    where modifyLayout' = modifyLayout . magicCompose 

Una questione collegata: forse c'è un modo migliore per esprimere lo stesso concetto?

Per riferimento, modifyLayout è

modifyLayout :: (CC m Window) 
      => (forall l. (LayoutClass l Window) => l Window -> m l Window) 
      -> ConfigMonad 

Chiarificazione (EDIT):

L'idea alla base utilizzando il tipo di composizione è presente.

considerare due modificatori di layout,

m1 :: LayoutClass l a => l a -> M1 l a 

e

m2 :: LayoutClass l a => l a -> M2 l a 

Se compongo questi due, ottengo

m1m2 :: (LayoutClass l a, LayoutClass (M2 l) a) => l a -> M1 (M2 l) a 
m1m2 = m1 . m2 

possiamo assumere v'è un instance LayoutClass l a => LayoutClass (M2 l) a. Mentre ci si trova, supponiamo anche che ci siano istanze per CC M1 Window e CC M2 Window.

Se io ora cerco di alimentare questo in modifyLayout definito in precedenza:

modifyLayout m1m2 

GHC immediatamente si confonde con tipi nidificati e si lamenta:

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 

usando il tipo di composizione, posso rimediare che, dal momento che GHC corrisponde a M1 :. M2 con m nella firma modifyLayout ed evita l'intera confusione di annidamento.Digitare sinonimi ovviamente non avrà questa proprietà.

UPDATE:

Dopo un po 'frugare, ho trovato una soluzione parziale (non so perché non ho pensato di esso prima, ma vabbè)

E' possibile definire un typeclass come questo

class S l f t | f l -> t where 
    sq :: (l a -> f a) -> (l a -> t l a) 

La dipendenza funzionale assicura che il compilatore sarà in grado di selezionare l'istanza da solo.

Poi, diventa possibile scrivere casi come questo

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 

Questo risponde in parte alla mia domanda: sq racchiude una pila di trasformazione, a condizione che vi sia un'istanza definita per un dato livello di nidificazione.

Tuttavia, queste istanze sembrano implorare una definizione di istanza ricorsiva. Per ora, non ero in grado di capire come sarebbe esattamente quello. Quindi, qualsiasi intuizione è benvenuta.

+0

Forse, coercizioni sicuri: https://wiki.haskell.org/GHC/Coercible – chi

+0

È il newtype davvero necessario? Non vedo immediatamente perché qualcosa come 'type ((f :: (* -> *) -> * -> *):. (G :: (* -> *) -> * -> *)) la = f (gl) a' non funzionerebbe. Non avresti bisogno di usare 'Componi' allora. – madidier

+0

@chi, ci ho provato. GHC si confonde con i tipi abbastanza velocemente, quindi richiede delle annotazioni di tipo esplicito, che in qualche modo sconfiggono lo scopo. – lierdakil

risposta

2

Grazie a Adam Vogt (@aavogt), sono riuscito a raggiungere una conclusione soddisfacente.

Ero sulla strada giusta con istanze di classe S. Risulta che l'inversione della dipendenza dell'istanza consente a typechecker di dedurre altre istanze. Tuttavia, l'estensione IncoherentInstances è necessaria per la terminazione della ricorsione (vale a dire il caso base).

Ecco il codice:

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 .) 
Problemi correlati