💾 Archived View for alsd.eu › it › 2021-07-15-oggetti-tramite-chiusure.gmi captured on 2023-07-22 at 16:26:34. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2022-06-11)
-=-=-=-=-=-=-
Una delle idee più interessanti che ho trovato fin'ora leggendo SICP¹ è l'implementazione di oggetti da "zero". Ad esempio, la classica cella cons dei Lisp può essere costruita così:
(define (cons x y) (lambda (f) (f x y))) (define (car z) (z (lambda (x y) x))) (define (cdr z) (z (lambda (x y) y)))
Per chi non digerisce le parentesi ecco l'equivalente in Python:
def pair(x, y): return lambda f: f(x, y) def first(z): return z(lambda x, y: x) def second(z): return z(lambda x, y: y) p = pair(4, 2) second(p) # => 2
In questo esempio si definisce una coppia come una funzione che presa in input un'altra funzione la applica ai due elementi della coppia, che vengono memorizzati dal meccanismo delle chiusure lessicali.
Possiamo anche creare strutture con più campi:
(define (make-user name phone address) (lambda (n) (case n ((0) name) ((1) phone) ((2) address)))) (define (user-name u) (u 0)) (define (user-phone u) (u 1)) (define (user-address u) (u 2))
Per poi usarle così:
(define pippo (make-user "Pippo" "12467620823" "Via Roma")) (user-name pippo) ;; => "Pippo"
In questo caso abbiamo usato un approccio forse concettualmente più semplice, l'oggetto è una funzione che prende in input un numero e restituisce il valore del campo corrispondente.
Tutto ciò ci permette di creare semplici strutture immutabili, ma se volessimo di più?
(define (make-account) (define balance 0) (lambda (msg) (case msg ((balance) balance) ((deposit) (lambda (amount) (set! balance (+ balance amount)))) ((withdraw) (lambda (amount) (if (> amount balance) (error "insufficient balance") (set! balance (- balance amount))))) (else (error "undefined property or method" msg)))))
Ora il risultato di (make-account) è una procedura che riceve in input un simbolo e restituisce o il bilancio del conto bancario, oppure le operazioni di deposito e prelievo (a loro volta procedure). Anche adesso la proprietà balance viene memorizzata tramite chiusure lessicali. L'utilizzo è il seguente:
(define acc (make-account)) (acc 'balance) ;; => 0 ((acc 'deposit) 100) (acc 'balance) ;; => 100
Il modo in cui interagiamo con l'oggetto, ovvero passandogli un "messaggio" e utilizzando la risposta, dà il nome di "message passing" a questa tecnica di implementazione.
Possiamo naturalmente semplificare l'utilizzo dell'oggetto nascondendo il messaggio in procedure apposite:
(define (account-balance account) (account 'balance)) (define (account-deposit account amount) ((account 'deposit) amount)) (define (account-withdraw account amount) ((account 'withdraw) amount)) (account-withdraw acc 25) (account-balance acc) ;; => 75
È veramente affascinante quello che si può fare avendo a disposizione un modo per definire funzioni e poco altro!
¹Structure and Interpretation of Computer Programs (SICP)
🄯 il contenuto di questa capsula è sotto licenza CC-BY-SA 4.0