Dai un'occhiata a CLOS, il Common Lisp Object System , descritto in particolare in Oggetto- Programmazione programmata in Common Lisp: Guida di un programmatore a CLOS nel 1988. La stessa CLOS è stata influenzata da sistemi di oggetti esistenti come Sapori e CommonLoops .
Il seguente definisce o ridefinisce una classe quando viene chiamato:
(defclass my-class (multiple parent classes)
((x :initarg :x)
(y :initarg :y)))
Questa è una macro, ma esiste l'equivalente funzionale e si chiama ensure-class
, che è più facile da usare quando gli argomenti vengono calcolati.
Il valore restituito da questa espressione o da (find-class 'my-class)
, è un valore di tipo standard-class
. Le classi hanno un metaclass , che è un oggetto che descrive come rappresentare le classi e come accedere ai valori dello slot. Puoi cambiare CLOS per farlo comportarsi come un altro sistema di oggetti grazie al protocollo del metaoggetto , descritto in The Art of the Metaobject protocol (aka AMOP). Ad esempio, potresti fornire un argomento :metaclass
che rende i tuoi oggetti serializzati e deserializzati da un database.
Una volta che hai una lezione, puoi installarla:
(make-instance 'my-class :x 10 :y 20)
Si noti che l'argomento della classe a make-instance
può essere fornito in fase di runtime.
Tutti i valori in Common Lisp hanno una classe:
(class-of 3)
=> #<built-in-class fixnum>
(class-of '(a b c))
=> #<built-in-class cons>
Ma non tutto è necessariamente un oggetto.
CLOS ti consente di definire multimetodi e quindi le funzioni generiche non sono legate a un singolo classe. Ad esempio:
(defgeneric attack (attacker target)
(:method ((w wizard) (v vampire))
;; Vampires do not glitter when exposed to light, they burn.
(cast-spell w 'sunbath :on v))
(:method ((w wizard) (d dragon))
;; Fall, you fool
(cast-spell w 'paralysis :on d))
;; Dispatch on human here, a superclass of wizard
(:method ((v vampire) (h human)) (bite v h))
(:method ((d dragon) (h human)) (ignite d h))
;; Dragon duel
(:method ((d1 dragon) (d2 dragon) (bite d1 d2))
;; Default case
(:method (x y) (punch x y)))
Puoi aggiungere o rimuovere metodi in fase di runtime e specificare anche altri tipi di metodi, come :after
, :before
o :around
metodi:
(defmethod attack :around (attacker target)
(when (>= (roll-dice (attack-points attacker))
(defense-points target))
;; attack is successful, proceed to actual attack
(call-next-method)))
Quanto sopra viene eseguito attorno ad ogni attacco e verifica se l'attaccante ha successo, in base al caso e alle caratteristiche di entrambi gli oggetti. L'espressione (call-next-method)
viene utilizzata per chiamare il successivo metodo :around
meno specifico o il successivo metodo primario più specifico se non esiste un altro metodo :around
.
Gli oggetti possono cambiare classe in fase di runtime:
(defmethod bite ((v vampire) (h human))
(take-hit h (- (bite-attack v) (armor h)))
(change-class h (infected (class-of h))
:target-class 'vampire
:delay-turns 3))
Qui, un vampiro infligge un danno a una procedura guidata che quindi si trasforma in una classe infected-wizard
, grazie a (infected (class-of h))
che assicura che tale classe esista creando se necessario (richiede alexandria e più vicino -mop ):
(defun infected (class)
(ensure-class
(symbolicate 'infected "-" (class-name class))
:direct-superclasses (list 'infected class)))
L'istanza di human viene aggiornata con nuove slot: qui, il wizard dovrebbe essere trasformato in un generico vampiro in tre turni.
how would you give this functionality while keeping language consistent?
Cambiare classi e metodi al runtime potrebbe essere problematico, ma il sistema è costruito attorno a un protocollo robusto per gestire le modifiche al runtime, come chiamare i metodi di inizializzazione appropriati quando si modifica la classe di un oggetto, o ricalcolare gli elenchi di precedenza della classe prima che le funzioni generiche vengano chiamate .
... but then you loose type-checking
Il controllo dei tipi viene applicato in fase di esecuzione.