Cercherò di presentare qui un "idiomatica" singletons
soluzione (se una cosa del genere esiste anche). Preliminari:
{-# LANGUAGE
RankNTypes, DataKinds, PolyKinds, ConstraintKinds, GADTs,
TypeOperators, MultiParamTypeClasses, TypeFamilies, ScopedTypeVariables #-}
import Data.Singletons.Prelude
import Data.Proxy
import GHC.Exts (Constraint)
-- SingI constraint here for simplicity's sake
class SingI name => Analyze (name :: Symbol) ty where
analyze :: Proxy name -> ty -> Int
data Rec rs where
Nil :: Rec '[]
Cons :: ty -> Rec rs -> Rec ('(name, ty) ': rs)
recName :: Rec ('(name, t) ': rs) -> Proxy name
recName _ = Proxy
abbiamo bisogno di un All c rs
vincolo, ma noi dare un giro e fare c
un TyFun
invece una pianura a -> Constraint
costruttore:
type family AllC (c :: TyFun a Constraint -> *) (rs :: [a]) :: Constraint where
AllC c '[] =()
AllC c (x ': xs) = (c @@ x, AllC c xs)
TyFun
ci consente di astratto su costruttori di tipo e tipo famiglie e ci dà un'applicazione parziale. Ci dà quasi funzioni di livello base di prima classe con una sintassi alquanto brutta. Nota però che perdiamo necessariamente l'iniettività del costruttore. @@
è l'operatore per l'applicazione di TyFun
-s. TyFun a b -> *
significa che a
è l'input e è l'output e il trailing -> *
è solo un artefatto della codifica. Con GHC 8.0 saremo in grado di fare solo
type a ~> b = TyFun a b -> *
E utilizzare a ~> b
da allora in poi.
Siamo in grado di attuare ora un generale mappatura "di classe" over Rec
:
cMapRec ::
forall c rs r.
AllC c rs => Proxy c -> (forall name t. (c @@ '(name, t)) => Proxy name -> t -> r) -> Rec rs -> [r]
cMapRec p f Nil = []
cMapRec p f [email protected](Cons x xs) = f (recName r) x : cMapRec p f xs
noti che sopra c
è tipo TyFun (a, *) Constraint -> *
.
e quindi implementare analyzeRec
:
analyzeRec ::
forall c rs. (c ~ UncurrySym1 (TyCon2 Analyze))
=> AllC c rs => Rec rs -> [(String, Int)]
analyzeRec = cMapRec (Proxy :: Proxy c) (\p t -> (fromSing $ singByProxy p, analyze p t))
In primo luogo, c ~ UncurrySym1 (TyCon2 Analyze)
è solo un tipo di livello let
vincolante che mi permette di usare c
in Proxy c
come abbreviazione. (Se volessi davvero usare tutti i trucchi sporchi, aggiungerei {-# LANGUAGE PartialTypeSignatures #-}
e scrivere Proxy :: _ c
).
UncurrySym1 (TyCon2 Analyze)
fa la stessa cosa che uncurry Analyze
farebbe se Haskell avesse pieno supporto per le funzioni di livello tipo. L'ovvio vantaggio qui è che possiamo scrivere immediatamente il tipo di analyzeRec
senza famiglie o classi di tipi di livello superiore extra, e usare anche AllC
molto più in generale.
Come bonus, cerchiamo di rimuovere il vincolo SingI
da Analyze
, e cercare di attuare analyzeRec
.
class Analyze (name :: Symbol) ty where
analyze :: Proxy name -> ty -> Int
Ora dobbiamo richiedere una ulteriore vincolo che esprime che tutti i name
-s nel nostro Rec
sono SingI
.Possiamo usare due cMapRec
-s, e zip i risultati:
analyzeRec ::
forall analyze names rs.
(analyze ~ UncurrySym1 (TyCon2 Analyze),
names ~ (TyCon1 SingI :.$$$ FstSym0),
AllC analyze rs,
AllC names rs)
=> Rec rs -> [(String, Int)]
analyzeRec rc = zip
(cMapRec (Proxy :: Proxy names) (\p _ -> fromSing $ singByProxy p) rc)
(cMapRec (Proxy :: Proxy analyze) (\p t -> analyze p t) rc)
Qui TyCon1 SingI :.$$$ FstSym0
può essere tradotto come SingI . fst
.
Questo è ancora approssimativamente nel livello di astrazione che può essere prontamente espresso con TyFun
-s. Il limite principale, ovviamente, è la mancanza di lambda. Idealmente non dovremmo usare zip
, invece useremmo AllC (\(name, t) -> (SingI name, Analyze name t))
e usare un singolo cMapRec
. Con singletons
, se non siamo più in grado di utilizzarlo con la programmazione a livello di codice senza punti, dobbiamo introdurre una nuova famiglia di tipi puntuali.
In modo divertente, GHC 8.0 sarà abbastanza potente da consentire l'implementazione di lambda di livello testo da zero, anche se probabilmente sarà brutta da morire. Ad esempio, \p -> (SingI (fst p), uncurry Analyze p)
potrebbe essere simile a questa:
Eval (
Lam "p" $
PairL :@@
(LCon1 SingI :@@ (FstL :@@ Var "p")) :@@
(UncurryL :@@ LCon2 Analyze :@@ Var "p"))
dove tutti i L
suffissi indicano le immersioni termine lambda di ordinaria TyFun
-s (ancora un altro insieme di abbreviazioni da generare da TH ...).
Ho un prototype, anche se funziona ancora con le variabili ancora più brutte di de Bruijn, a causa di un bug di GHC. Dispone anche di Fix
e di pigrizia esplicita a livello di testo.
Fantastico! Avevo appena capito qualcosa come "AllC" ed ero rimasto bloccato nel comprendere i simboli delle funzioni. Penso di poter fare questo lavoro. Mi sento come se il percorso completo di cMap fosse troppo lungo al momento, ma è praticamente fattibile! –
Non esitate a chiedere su 'TyFun'-s. Fanno coinvolgere un sacco di rumore sintattico, e probabilmente vale la pena di un'esposizione. Inoltre, puoi guardare [this] (https://typesandkinds.wordpress.com/2013/04/01/defunctionalization-for-the-win/), se non l'hai già letto. –
Ho sfogliato quel post un po 'indietro ma penso che ora ho lo sfondo per capirlo meglio, grazie ancora per il riferimento! –