2013-04-25 10 views

risposta

18

Questo può coprire uno veramente un'ampia varietà di tecniche diverse. Il più semplice è fondamentalmente inevitabile: se si desidera un valore che può essere nullo, che può dipendere dallo stato mutabile o dall'input dell'utente, è necessario contrassegnarlo con il sistema di tipi. Questo è ciò che Maybe, ST e IO do rispettivamente. Quindi se hai qualcosa che non è in uno dei tre tipi sopra, sai che deve essere un valore referentially trasparente che non può essere nullo.

Le tecniche di cui sopra sono molto fondamentali per il linguaggio e sostanzialmente inevitabili. Tuttavia, ci sono altri modi per utilizzare il sistema di tipi per migliorare la sicurezza e la correttezza che sono un po 'più interessanti.

Un utile esempio è la prevenzione dell'iniezione SQL. L'iniezione SQL è un problema comune nelle applicazioni Web: per l'idea di base, consulta this XKCD cartoon. Possiamo effettivamente utilizzare il sistema di tipi per garantire che qualsiasi stringa passata al database sia stata disinfettata. L'idea di base è quella di creare un nuovo tipo per le stringhe "grezzi":

newtype Raw a = Raw a 

Poi, assicurarsi che tutte le funzioni per ottenere input da parte dell'utente di ritorno Raw valori invece di stringhe normali. Infine, basta una sanificazione:

sanitize :: Raw String -> String 

Poiché le funzioni normali accettano String piuttosto che Raw, non sarà in grado di passare accidentalmente in una stringa o grezzi. E poiché abbiamo definito Raw utilizzando newtype, non ha affatto sovraccarico di runtime.

Yesod, uno dei principali framework Web Haskell, utilizza una tecnica simile a questa per impedire l'iniezione SQL. Ha anche altri approcci interessanti come l'uso del sistema di tipi per prevenire collegamenti interrotti all'interno del database; Si dovrebbe controllare.

All'estremo estremo, è persino possibile utilizzare il sistema di tipi per garantire che le matrici siano della giusta dimensione. Ecco un modo molto semplice per farlo. In primo luogo, abbiamo bisogno di numeri tipo di livello: (. Stiamo utilizzando Peano Arithmetic al type level qui)

data Z 
data S n 

L'idea è semplice: Z è 0 e S è la funzione successore, così S Z è 1, è S (S Z) 2 e così via.

ora possiamo scrivere una funzione matrice moltiplicare sicura:

matMul :: Mat a b -> Mat b c -> Mat a c 

Questa funzione solo consente di moltiplicare le matrici se la dimensione interna corrisponde, e garantisce che la matrice risultante ha le dimensioni giuste nel suo tipo.

Type-safe matrix multiplication ha ulteriori dettagli.

7

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.

Problemi correlati