Beh, a mio parere, una delle cose che esposizioni come quella che hai visto spesso non riescono a trasmettere è che questa roba di correttezza per tipo è spesso roba che non accade "naturalmente", ma piuttosto che deriva dall'uso di tecniche per progettare i tuoi tipi che non sono ovvi per i programmatori che provengono da altre lingue. Uno dei miei esempi preferiti viene da the Haskell Wiki page on phantom types; se si guarda al capitolo 1 su quella pagina, hanno questo esempio (che IMO dovrebbe essere una dichiarazione newtype
invece di data
):
data FormData a = FormData String
Qual è la a
facendo? Bene, quello che fa è che lo fa artificialmente in modo che FormData "foo" :: FormData Validated
e FormData "foo" :: FormData Unvalidated
, nonostante il loro "davvero" sia lo stesso, ora hanno tipi incompatibili, e quindi puoi forzare il tuo codice a non mescolare l'uno e l'altro. Bene, mi permetta di non ripetere quello che dice la pagina, è relativamente facile da leggere (almeno Sezione 1).
Un esempio più complicato che ho utilizzato in uno dei miei progetti "on-and-off": gli ipercubi OLAP possono essere visti come una sorta di matrice indicizzata non da indici interi, ma da oggetti del modello dati come persone, giorni, linee di prodotti, ecc .:
-- | The type of Hypercubes.
data Hypercube point value = ...
-- | Access a data point in a hypercube.
get :: Eq point => Hypercube point value -> point -> value
-- | This is totally pseudocode...
data Salesperson = Mary | Joe | Irma deriving Eq
data Month = January | February | ... | December deriving Eq
data ProductLine = Widget | Gagdet | Thingamabob
-- Pseudo-example: compute sales numbers grouped by Salesperson, Month and
-- ProductLine for the combinations specified as the "frame"
salesResult :: HyperCube (Salesperson, Month, ProductLine) Dollars
salesResult = execute salesQuery frame
where frame = [Joe, Mary] `by` [March, April] `by` [Widgets, Gadgets]
salesQuery = ...
-- Read from salesResult how much Mary sold in Widgets on April.
example :: Dollars
example = get salesResult (Mary, April, Widgets)
Spero che abbia più senso di quanto temo. Comunque, il punto l'esempio è il seguente problema: il tipo di get
, come disposto qui, consente di chiedere ad un Hypercube
di dirvi il valore di un punto non ha:
badExample :: Dollar
badExample = get salesResult (Irma, January, Thingamabob)
Una possibile la soluzione a questo è di rendere l'operazione get
restituire Maybe value
anziché solo value
. Ma possiamo effettivamente fare di meglio; possiamo progettare un'API in cui è possibile chiedere a Hypercube
solo i valori che contiene. La chiave è simile all'esempio FormData
, ma una variante più sofisticata. In primo luogo si introduce questo tipo phantom:
data Cell tag point = Cell { getPoint :: point } deriving Eq
Ora riformuliamo Hypercube
e get
di essere sensibile al tag. In realtà, lo renderò più concreto in questo esempio riformulato. Iniziamo con questo:
{-# LANGUAGE ExistentialTypes #-}
data AuxCube tag point value =
AuxCube { getFrame :: [Cell tag point]
, get :: Cell tag point -> value }
-- This is using a type system extension called ExistentialTypes:
data Hypercube point value = forall tag. Hypercube (AuxCube tag point value)
-- How to use one of these cubes. Suppose we have:
salesResult :: Hypercube (Salesperson, Month, ProductLine) Dollars
salesResult = execute salesQuery points
where points = [Joe, Mary] `by` [March, April] `by` [Widgets, Gadgets]
salesQuery = ...
-- Now to read values, we have to do something like this:
example = case salesResult of
Hypercube (AuxCube frame getter) -> getter (head frame)
mi scuso se l'uso di ExistentialTypes
si confonde qui, ma per fare una lunga storia breve, che cosa fa in questo esempio è fondamentalmente che ogni Hypercube
contiene un AuxCube
con un tag anonima unico digitare il parametro, in modo che ora nessun due Hypercube
s possa avere Cell
s dello stesso tipo. E per questo motivo, se utilizziamo il sistema del modulo per impedire ai chiamanti di costruire Cell
s, i chiamanti non possono probabilmente chiedere un Hypercube
per un Cell
per il quale non ha un valore.
Crediti: I learned this technique by asking here in Stack Overflow.