2015-05-26 14 views
12

Sto passando attraverso Contracts in the Racket Guide.Quando è -> effettivamente utile nella racchetta?

Il ->i costrutto permette di posizionare vincoli arbitrari sull'ingresso/uscita di una funzione.

Per esempio, potrei avere una funzione unzip che prende una lista di coppie e restituisce due liste. Usando i contratti, potrei confermare che ogni elemento dell'elenco è una coppia e che gli out-list hanno gli elementi corrispondenti.

La guida racchetta suggerisce che questo è quando i contratti sono utili. Ma sembra che sarebbe meglio farlo all'interno della funzione stessa. Potrei lanciare un errore se incontro un non-paio, questo controllerebbe l'in-list. L'uscita viene automaticamente controllata avendo una funzione corretta.

Qual è un esempio concreto di cui il codice è migliorata in qualche modo da un contratto più complesso di tipi semplici?

risposta

13

Come avete descritto, praticamente qualsiasi controllo che può essere eseguita in ->i può essere eseguita all'interno della funzione stessa, ma poi di nuovo, qualsiasi controllo eseguito dai contratti può, per la maggior parte, essere eseguita entro le funzioni stesse . La codifica delle informazioni in un contratto offre alcuni vantaggi.

  1. È possibile estrarre gli invarianti dall'implementazione della funzione. Questo è bello perché non hai bisogno di ingombrare la funzione stessa con clausole di guardia, puoi semplicemente scrivere il codice e conoscere che gli invarianti saranno mantenuti dal contratto.
  2. I contratti sono difficili da fornire una buona segnalazione degli errori. Assegneranno automaticamente "colpa" alla parte che viola il contratto e, per contratti complessi, aggiungeranno il contesto ai messaggi di errore per rendere il più chiaro possibile il problema.

Questi sono più evidenti con ->i quando il contratto deve specificare dipendenze all'interno argomenti forniti alla funzione. Ad esempio, dispongo di una raccolta di raccolte, che include una funzione subsequence. Richiede tre argomenti, una sequenza, un indice iniziale e un indice finale. Questo è il contratto che uso per proteggerlo:

(->i ([seq sequence?] 
     [start exact-nonnegative-integer?] 
     [end (start) (and/c exact-nonnegative-integer? (>=/c start))]) 
    [result sequence?]) 

Questo mi permette di specificare in modo esplicito che l'indice finale deve essere maggiore o uguale a l'indice di partenza, e non devono preoccuparsi di verificare che invariante nella mia funzione. Quando ho violato il presente contratto, ottengo un bel messaggio di errore:

> (subsequence '() 2 1) 
subsequence: contract violation 
    expected: (and/c exact-nonnegative-integer? (>=/c 2)) 
    given: 1 
    which isn't: (>=/c 2) 

Può essere utilizzato per garantire invarianti più complessi, anche. Definisco anche la mia funzione map, che, come il built-in map di Racket, supporta numeri variabili di argomenti. La procedura fornita a map deve accettare lo stesso numero di argomenti in base alla sequenza fornita. Io uso il seguente contratto per map:

(->i ([proc (seqs) (and/c (procedure-arity-includes/c (length seqs)) 
          (unconstrained-domain-> any/c))]) 
    #:rest [seqs (non-empty-listof sequence?)] 
    [result sequence?]) 

Questo contratto garantisce due cose. Prima di tutto, l'argomento proc deve accettare lo stesso numero di argomenti quante sono le sequenze, come menzionato sopra. Inoltre, richiede anche che quella funzione restituisca sempre un singolo valore, poiché le funzioni Racket possono restituire più valori.

Questi invarianti sarebbe molto difficile da controllare all'interno del corpo della funzione in quanto, in particolare con il secondo invariante, devono essere ritardata fino funzione stessa è applicata . Deve anche essere verificato con ogni invocazione della funzione. Contratti, d'altra parte, avvolgere la funzione e gestirlo automaticamente.

Vuoi sempre codificare ogni singolo invariante di una funzione in un contratto? Probabilmente no. Ma se vuoi quel livello extra di controllo, è disponibile ->i.