Ho sperimentato l'utilizzo di famiglie di tipi per astrarre i toolkit dell'interfaccia utente. Mi sono sbloccato mentre provo a utilizzare HLists (http://homepages.cwi.nl/~ralf/HList/) per migliorare l'API.Come posso creare liste eterogenee (dette anche HLists) con elementi vincolati?
mio API inizialmente sembrava qualcosa di simile:
{-# LANGUAGE TypeFamilies #-}
class UITK tk where
data UI tk :: * -> *
stringEntry :: (UITK tk) => UI tk String
intEntry :: (UITK tk) => UI tk Int
tuple2UI :: (UI tk a,UI tk b) -> (UI tk (a,b))
tuple3UI :: (UI tk a,UI tk b,UI tk c) -> (UI tk (a,b,c))
tuple4UI :: (UI tk a,UI tk b,UI tk c,UI tk d) -> (UI tk (a,b,c,d))
ui :: (UITK tk) => (UI tk (String,Int))
ui = tuple2UI (stringEntry,intEntry)
questo funziona, ma il combinatore interfaccia utente lavora su tuple, e quindi ho bisogno di una funzione diversa per ogni dimensione tupla. Pensavo di poter usare qualcosa come HLists, ma o non è possibile, (o si spera) mi manca solo il tipo-fu necessario.
Ecco il mio tentativo:
{-# LANGUAGE TypeFamilies,FlexibleInstances,MultiParamTypeClasses #-}
-- A heterogeneous list type
data HNil = HNil deriving (Eq,Show,Read)
data HCons e l = HCons e l deriving (Eq,Show,Read)
-- A list of UI fields, of arbitrary type, but constrained on their
-- tk parameter. The StructV associated type captures the return
-- type of the combined UI
class (UITK tk) => FieldList tk l
where type StructV tk l
instance (UITK tk) => FieldList tk HNil
where type StructV tk HNil = HNil
instance (UITK tk, FieldList tk l) => FieldList tk (HCons (UI tk a) l)
where type StructV tk (HCons (UI tk a) l) = (HCons a (StructV tk l))
fcons :: (UITK tk, FieldList tk l) => UI tk a -> l -> HCons (UI tk a) l
fcons = HCons
-- Now the abstract ui toolkit definition
class UITK tk where
data UI tk :: * -> *
stringEntry :: (UITK tk) => UI tk String
intEntry :: (UITK tk) => UI tk Int
structUI :: (FieldList tk l) => l -> (UI tk (StructV tk l))
-- this doesn't work :-(
ui :: (UITK tk) => (UI tk (HCons String (HCons Int HNil)))
ui = structUI (fcons stringEntry
(fcons intEntry
HNil))
La definizione alla fine mi dà parecchi errori, il primo dei quali è:
Z.hs:38:6:
Could not deduce (FieldList
tk (HCons (UI tk0 String) (HCons (UI tk1 Int) HNil)))
arising from a use of `structUI'
from the context (UITK tk)
bound by the type signature for
ui :: UITK tk => UI tk (HCons String (HCons Int HNil))
at Z.hs:(38,1)-(40,21)
Possible fix:
add (FieldList
tk
(HCons
(UI tk0 String) (HCons (UI tk1 Int) HNil))) to the context of
the type signature for
ui :: UITK tk => UI tk (HCons String (HCons Int HNil))
or add an instance declaration for
(FieldList tk (HCons (UI tk0 String) (HCons (UI tk1 Int) HNil)))
In the expression:
structUI (fcons stringEntry (fcons intEntry HNil))
In an equation for `ui':
ui = structUI (fcons stringEntry (fcons intEntry HNil))
Senza completamente la comprensione di questo, penso di poter vedere almeno uno dei problemi Non sto informando con successo il compilatore che i 3 tipi di parametri di tk sono tutti dello stesso tipo (cioè si riferisce a tk, tk0, tk1) sopra. Non lo capisco - il mio costruttore di fcons ha lo scopo di mantenere i parametri tk dell'interfaccia utente coerenti per l'HList costruito.
Questa è la mia prima esperienza con le famiglie di tipi e classi di tipi a più parametri, quindi è probabile che manchi qualcosa di fondamentale.
È possibile costruire elenchi eterogenei con elementi vincolati? Dove sto andando male?
Si potrebbe aggiungere 'mapUI :: (a -> b) -> UI tk a -> UI tk b'; quindi, avresti solo bisogno di 'zipUI :: UI tk a -> UI tk b -> UI tk (a, b)' e potresti appiattire le tuple annidate che risultano usando 'mapUI'. Ovviamente, ciò equivale a richiedere a 'UI tk' di essere un'istanza di' Applicative' (assumendo che 'pure' possa essere fornito' pure'), che io raccomanderei. – ehird
Quanto sopra è una semplificazione della mia vera API - Ho già mapUI. Ho anche giocato con qualcosa come l'approccio tuple annidato, ma in pratica non funziona così bene in quanto il codice che costruisce le UI concrete ha bisogno di vedere tutti i bambini contemporaneamente, non una coppia alla volta ... cioè la costruzione un'interfaccia utente composta deve essere una singola applicazione di una funzione n-ario, non un'applicazione ripetuta di una funzione binaria. – timbod
Abbastanza giusto. Suggerirei comunque di richiedere 'Functor (UI tk)' nella testa dell'istanza piuttosto che definire la propria funzione 'mapUI'. – ehird