Non riesco a pensare a un modo per implementare letteralmente o per Constraint
s, sfortunatamente, ma se stiamo solo mettendo insieme le uguaglianze, come nel tuo esempio, possiamo rendere più piccante l'approccio alla classe di tipo e renderlo chiuso con famiglie di tipi e booleans sollevati. Funzionerà solo in GHC 7.6 e versioni successive; alla fine, menziono entrambi come sarà più bello in GHC 7.8 e come eseguire il backport a GHC 7.4.
L'idea è questa: Proprio come abbiamo potuto dichiarare una funzione a livello di valore isBananaColor :: Color -> Bool
, così anche noi possiamo dichiarare una funzione di tipo a livello IsBananaColor :: Color -> Bool
:
type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black = True
type instance IsBananaColor White = False
type instance IsBananaColor Red = False
type instance IsBananaColor Blue = False
type instance IsBananaColor Tawny = False
type instance IsBananaColor Purple = False
Se ci piace, possiamo anche aggiungere
type BananaColor c = IsBananaColor c ~ True
Abbiamo poi ripetere questo per ogni colore di frutta, e definiscono Fruit
come nel tuo secondo esempio:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
Banana :: BananaColor c => Fruit c
Apple :: AppleColor c => Fruit c
Grape :: GrapeColor c => Fruit c
Orange :: OrangeColor c => Fruit c
type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black = True
type instance IsBananaColor White = False
type instance IsBananaColor Red = False
type instance IsBananaColor Blue = False
type instance IsBananaColor Tawny = False
type instance IsBananaColor Purple = False
type BananaColor c = IsBananaColor c ~ True
type family IsAppleColor (c :: Color) :: Bool
type instance IsAppleColor Red = True
type instance IsAppleColor Green = True
type instance IsAppleColor White = False
type instance IsAppleColor Blue = False
type instance IsAppleColor Yellow = False
type instance IsAppleColor Tawny = False
type instance IsAppleColor Purple = False
type instance IsAppleColor Black = False
type AppleColor c = IsAppleColor c ~ True
type family IsGrapeColor (c :: Color) :: Bool
type instance IsGrapeColor Red = True
type instance IsGrapeColor Green = True
type instance IsGrapeColor White = True
type instance IsGrapeColor Blue = False
type instance IsGrapeColor Yellow = False
type instance IsGrapeColor Tawny = False
type instance IsGrapeColor Purple = False
type instance IsGrapeColor Black = False
type GrapeColor c = IsGrapeColor c ~ True
-- For consistency
type family IsOrangeColor (c :: Color) :: Bool
type instance IsOrangeColor Tawny = True
type instance IsOrangeColor White = False
type instance IsOrangeColor Red = False
type instance IsOrangeColor Blue = False
type instance IsOrangeColor Yellow = False
type instance IsOrangeColor Green = False
type instance IsOrangeColor Purple = False
type instance IsOrangeColor Black = False
type OrangeColor c = IsOrangeColor c ~ True
(Se si desidera, è possibile sbarazzarsi di -XConstraintKinds
ei type XYZColor c = IsXYZColor c ~ True
tipi, e solo definire i costruttori di Fruit
come XYZ :: IsXYZColor c ~ True => Fruit c
.)
Ora, che cosa fa questo si acquista, e ciò che non è vero che si compra ? Tra i lati positivi, si ottiene la possibilità di definire il proprio tipo come si desidera, che è sicuramente una vittoria; e dal momento che Color
è chiuso, nessuno può aggiungere più tipi di istanze familiari e interromperlo.
Tuttavia, ci sono aspetti negativi. Non ottieni l'inferenza che volevi dirti automaticamente che [Apple, Grape, Banana]
è di tipo Fruit Green
; la cosa peggiore è che il [Apple, Grape, Banana]
ha il tipo perfettamente valido (AppleColor c, GrapeColor c, BananaColor c) => [Fruit c]
. Sì, non c'è modo di monomorfizzarlo, ma GHC non riesce a capirlo. Ad essere onesti, non riesco a immaginare nessuna soluzione che ti dia queste proprietà, anche se sono sempre pronto ad essere sorpreso. L'altro ovvio problema con questa soluzione è come lungo è-è necessario definire tutti gli otto casi di colore per ciascuna famiglia di tipi IsXYZColor
! (L'uso di un nuovo tipo di famiglia per ciascuno è anche fastidioso, ma inevitabile con soluzioni di questo modulo.)
ho già detto che GHC 7.8 renderà questo più bello; lo farà ovviando alla necessità di elencare ogni singolo caso per ogni singola classe IsXYZColor
. Come? Bene, Richard Eisenberg et al. introdotto chiuso sovrapposto famiglie di tipo ordinato in HEAD GHC, e sarà disponibile in 7.8. C'è a paper in sumbission to POPL 2014 (e uno extended version) sull'argomento e Richard ha anche scritto an introductory blog post (che sembra avere una sintassi obsoleta).
L'idea è di consentire che le istanze di tipo famiglia siano dichiarate come funzioni ordinarie: le equazioni devono essere tutte dichiarate in un unico posto (rimuovendo l'ipotesi del mondo aperto) e vengono provate in ordine, il che consente la sovrapposizione. Qualcosa di simile
type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black = True
type instance IsBananaColor c = False
è ambigua, perché IsBananaColor Green
partite sia il primo e l'ultimo equazioni; ma in una funzione ordinaria, funzionerebbe bene. Così la nuova sintassi è:
type family IsBananaColor (c :: Color) :: Bool where
IsBananaColor Green = True
IsBananaColor Yellow = True
IsBananaColor Black = True
IsBananaColor c = False
Che type family ... where { ... }
blocco definisce il tipo di famiglia nel modo che desidera definirla; segnala che questa famiglia di tipi è chiusa, ordinata e sovrapposta, come descritto sopra. Così, il codice sarebbe diventato qualcosa di simile a quanto segue in GHC 7.8 (non testato, come io non ce l'ho installato sulla mia macchina):
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
Banana :: IsBananaColor c ~ True => Fruit c
Apple :: IsAppleColor c ~ True => Fruit c
Grape :: IsGrapeColor c ~ True => Fruit c
Orange :: IsOrangeColor c ~ True => Fruit c
type family IsBananaColor (c :: Color) :: Bool where
IsBananaColor Green = True
IsBananaColor Yellow = True
IsBananaColor Black = True
IsBananaColor c = False
type family IsAppleColor (c :: Color) :: Bool where
IsAppleColor Red = True
IsAppleColor Green = True
IsAppleColor c = False
type IsGrapeColor (c :: Color) :: Bool where
IsGrapeColor Red = True
IsGrapeColor Green = True
IsGrapeColor White = True
IsGrapeColor c = False
type family IsOrangeColor (c :: Color) :: Bool where
IsOrangeColor Tawny = True
IsOrangeColor c = False
Evviva, siamo in grado di leggere senza addormentarsi dalla noia! In effetti, noterai che sono passato alla versione esplicita IsXYZColor c ~ True
per questo codice; L'ho fatto perché perché lo standard per i quattro sinonimi in più è diventato molto più ovvio e fastidioso con queste definizioni più brevi!
Tuttavia, andiamo nella direzione opposta e rendere questo codice più brutto. Perché? Bene, GHC 7.4 (che è, ahimè, quello che ho ancora sulla mia macchina) non supporta le famiglie di tipi con tipo di risultato non-*
. Cosa possiamo fare invece? Possiamo usare classi di tipi e dipendenze funzionali per fingere. L'idea è che al posto di IsBananaColor :: Color -> Bool
, abbiamo una classe di tipo IsBananaColor :: Color -> Bool -> Constraint
e aggiungiamo una dipendenza funzionale dal colore al booleano. Quindi IsBananaColor c b
è soddisfacente se e solo se IsBananaColor c ~ b
nella versione più bella; perché Color
è chiuso e abbiamo una dipendenza funzionale da esso, questo ci dà comunque le stesse proprietà, è solo più brutto (anche se per lo più concettualmente così). Senza ulteriori indugi, il codice completo:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleContexts #-}
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
Banana :: BananaColor c => Fruit c
Apple :: AppleColor c => Fruit c
Grape :: GrapeColor c => Fruit c
Orange :: OrangeColor c => Fruit c
class IsBananaColor (c :: Color) (b :: Bool) | c -> b
instance IsBananaColor Green True
instance IsBananaColor Yellow True
instance IsBananaColor Black True
instance IsBananaColor White False
instance IsBananaColor Red False
instance IsBananaColor Blue False
instance IsBananaColor Tawny False
instance IsBananaColor Purple False
type BananaColor c = IsBananaColor c True
class IsAppleColor (c :: Color) (b :: Bool) | c -> b
instance IsAppleColor Red True
instance IsAppleColor Green True
instance IsAppleColor White False
instance IsAppleColor Blue False
instance IsAppleColor Yellow False
instance IsAppleColor Tawny False
instance IsAppleColor Purple False
instance IsAppleColor Black False
type AppleColor c = IsAppleColor c True
class IsGrapeColor (c :: Color) (b :: Bool) | c -> b
instance IsGrapeColor Red True
instance IsGrapeColor Green True
instance IsGrapeColor White True
instance IsGrapeColor Blue False
instance IsGrapeColor Yellow False
instance IsGrapeColor Tawny False
instance IsGrapeColor Purple False
instance IsGrapeColor Black False
type GrapeColor c = IsGrapeColor c True
class IsOrangeColor (c :: Color) (b :: Bool) | c -> b
instance IsOrangeColor Tawny True
instance IsOrangeColor White False
instance IsOrangeColor Red False
instance IsOrangeColor Blue False
instance IsOrangeColor Yellow False
instance IsOrangeColor Green False
instance IsOrangeColor Purple False
instance IsOrangeColor Black False
type OrangeColor c = IsOrangeColor c True
Hai visto [GHC 7.4 s 'caratteristica promozione genere +] (http://www.haskell.org/ghc/docs/7.6. 1/html/users_guide/promotion.html)? Permette di trattare tipi come tipi e valori come tipi. Con '-XDataKinds',' Green' è una cosa del tipo di livello del testo perfettamente valida 'Color'. Ciò rende la programmazione a livello di codice più sicura per i tipi (dato che le cose sono codificate, 'Fruit Int' è ben congegnato), e rimuove la necessità di definire i tipi spuri' dati Green = Green'. –