Esiste un uso pratico per il tipo vuoto in Common Lisp?

4

La specifica Common Lisp afferma che nil è il nome del tipo vuoto , ma non ho mai trovato alcuna situazione in Common Lisp in cui mi sembrava il tipo vuoto era utile / necessario. È lì solo per completezza (e rimuoverlo non causerebbe alcun danno a nessuno)? O c'è davvero qualche utilità per il tipo vuoto in Common Lisp? Se sì, allora preferirei una risposta con un esempio di codice.

Ad esempio, in Haskell il tipo vuoto può essere usato quando si vincolano strutture dati estranee, per assicurarsi che nessuno cerchi di creare valori di quel tipo senza usare l'interfaccia esterna della struttura dati (sebbene in questo caso, il tipo non è veramente vuoto).

    
posta Pedro Rodrigues 18.07.2014 - 13:28
fonte

2 risposte

6

Dichiarazioni di tipo e analisi statiche

Poiché nessun valore digitato come NIL può esistere, l'uso del tipo NIL è per lo più rilevante per l'analisi statica. T e nil rappresentano rispettivamente i tipi in alto (⊤) e in basso (⊥) e potrebbero essere utilizzati per inferire il tipo di espressioni. Considera il seguente esempio:

(defun the-thing () (length (1+ (read))))
  • Il tipo di (read) è T , perché se la lettura ha esito positivo, il valore restituito può essere qualsiasi cosa.
  • Il tipo di (1+ (read)) è necessariamente NUMBER , poiché il valore restituito esiste solo se non viene segnalato alcun errore. E se questo è il caso, sappiamo dall'analisi del tipo di + che il risultato è un numero.
  • Il tipo di (length (1+ (read))) è NIL , perché length restituisce un valore solo per sequenze . Fornendo un numero è garantito il segnale di un errore, e quindi i possibili valori restituiti dalla funzione sono rappresentati dal tipo vuoto (un insieme di valori vuoto).

Questo tipo di analisi non è solo teorica, ma in realtà accade con SBCL:

* (defun the-thing () (length (1+ (read))))
; in: DEFUN THE-THING
;     (LENGTH (1+ (READ)))
; 
; caught WARNING:
;   Derived type of (+ (READ) 1) is
;     (VALUES NUMBER &OPTIONAL),
;   conflicting with its asserted type
;     SEQUENCE.
;   See also:
;     The SBCL Manual, Node "Handling of Types"
; 
; compilation unit finished
;   caught 1 WARNING condition

THE-THING

Inoltre, qui possiamo vedere il tipo derivato di THE-THING :

* (describe #'the-thing)

#<FUNCTION THE-THING>
  [compiled function]

Lambda-list: ()
Derived type: (FUNCTION NIL NIL)
Source form:
  (SB-INT:NAMED-LAMBDA THE-THING
      NIL
    (BLOCK THE-THING (LENGTH (1+ (READ)))))

Il tipo NIL significa: questa funzione non verrà restituita correttamente. Ad esempio, in SBCL, la funzione error ha il seguente tipo dichiarato:

(FUNCTION (T &REST T) NIL)

... perché non restituisce mai un valore ma segnala una condizione. È possibile utilizzare NIL nelle dichiarazioni del tipo di funzione in modo esplicito se lo si desidera. A proposito, tieni presente che SBCL emette solo un avviso e comunque compila il tuo codice: puoi chiamare la funzione e attiverà i controlli dinamici che ti porteranno al debugger, ecc.

NIL implica l'assenza di terminazione "normale" . Se il corpo della tua funzione è (loop) , anche il tipo di ritorno è NIL . Ma a volte puoi tornare normalmente ma non hai alcun valore da restituire (come void in C). Questo è il motivo per cui (values &optional) è più appropriato come dichiarazione di tipo quando si restituisce nessun valore con successo . Questo viene fatto usando l'espressione (values) nel tuo codice. La parola chiave &optional nella dichiarazione del tipo è necessaria perché il tipo (values) ti consentirebbe di restituire più valori (vedi commenti).

È necessaria nil ?

A alcune implementazioni potrebbe non interessare nil . Poiché ti chiedi se potremmo rimuoverlo senza causare alcun danno, forse provare a rimuovere il tipo nil da un'implementazione potrebbe essere un buon esperimento, per vedere quanto effettivamente si rompe in pratica.

Tipi stranieri

Per quanto riguarda i tipi stranieri, dubito che possa essere usato come in Haskell, pur rispettando le specifiche, perché:

  1. il comportamento non è definito se un valore non corrisponde al tipo dichiarato e
  2. esiste già un tipo per i valori di foreing in CL (ma forse non ho capito esattamente cosa intendi con tipi stranieri e Haskell).

Questo è puramente speculativo, ma forse una variabile (o un array) di tipo nil assegnato da un runtime CL può essere gestito su un codice estraneo (utilizzando mezzi specifici dell'implementazione). Questo non è il modo in cui è fatto in CFFI .

Il caso di nil matrici

Non so perché (forse solo perché non c'è controllo) alcune implementazioni come SBCL permettono array di elementi di tipo nil , definiti come segue:

(type-of (make-array 10 :element-type nil) )
=> (SIMPLE-ARRAY NIL (10))

Tuttavia, non è possibile accedere ad un elemento di tale array:

(aref (make-array 10 :element-type nil) 5)

Viene rilevato il seguente errore di tipo: "Il valore 5 non è di tipo (MOD 4611686018427387901)." Inoltre, il backtrace contiene la seguente riga, in cui viene visualizzato il contenuto effettivo dell'array valori casuali:

(AREF "¿[¦^D^P^@^@^@×[" 5)

L'accesso agli array non inizializzati ha conseguenze indefinite (vedi commenti). Le conseguenze non sono definite anche se i dati effettivi sono solo referenziati e non utilizzati in modo efficace per il loro contenuto, come in:

(LENGTH (LIST (AREF (MAKE-ARRAY 1) 0)))

L'esempio è tratto dalla "Pubblicazione di UNINITIALIZED-ELEMENTS" .

Non vedo alcun motivo per consentire la creazione di array di tipo NIL in primo luogo, a parte forse per allocare memoria (inutile?). Come detto sopra, forse c'è un modo specifico per l'implementazione, totalmente non portatile, per alcuni Lisp di utilizzare tali array e passarli a funzioni estranee, ma ne dubito.

Infine, nel manuale di Maclisp (si noti che Maclisp precede il Common Lisp), si dice che gli array NIL possono essere usato come array di tipo T ma fare in modo che il garbage collector si comporti come se nessun oggetto fosse referenziato. Tuttavia, ecco cosa dice il manuale su di loro:

Type NIL arrays are discouraged even for those who think they know what they are doing.

    
risposta data 18.07.2014 - 16:01
fonte
2

Un uso pratico è generare errori quando l'implementazione tiene conto delle dichiarazioni. Potrebbe essere particolarmente utile nel codice generato dalla macchina e / o nel debug, oppure quando vuoi girare ad es. una variabile dinamica obsoleta e ad un certo punto la dichiarano di tipo nil , quindi leggendo da, scrivendo o legando segnalerà un errore (o è altrimenti un comportamento indefinito), ma il controllo di boundp non lo farà.

Un altro uso è se puoi passare un identificatore di tipo solo ad es. un'API di filtraggio, quindi nil (nessun valore è di tipo nil , nil è un sottotipo di qualsiasi tipo) può essere utile come t (qualsiasi valore è di tipo t , t è un supertipo di qualsiasi tipo), ma il contrario.

EDIT: serve anche come identificatore del tipo di base, poiché i tipi (or) e (member) sono equivalenti al tipo nil .

    
risposta data 20.08.2014 - 20:51
fonte

Leggi altre domande sui tag