Lisp – un DB semplice – 3

trudbolDov’ero rimasto? Ah sì! qui: Lisp – un DB semplice – 2, al punto in cui ho salvato il mio DB nel file di testo ~/lab/lisp/my-cds.db.

Avendo chiuso la REPL lo ricarico. Però devo anche ridefinire *db* la variabile globale che contiene il DB:

db0

E adesso posso ricaricarlo:

db1

Adesso ho un modo per caricare tutto il DB, ma spesso è utile visualizzare sono qualche record. Potrei usare grep o sed o AWK o altro ma non sarebbe lispico. Quello che voglio è qualcosa di simile a:

(select :artist "Dixie Chicks")

che mi ritorna solo i record delle Dixie Chicks.
Uh! le Pollastrelle del Sud, sì, qui il loro sito, ma sono fuori tema, scancello 😉

Uso la funzione remove-if-not che prende un predicato (funzione che ritorna un valore booleano) e una lista e ritorna nil per falso e qualsiasi altra cosa per vero.

Per esempio se voglio estrarre i pari da una lista di numeri posso scrivere

db2

In questo caso il predicato è la funzione evenp (finisce con p) che ritorna vero se il numero è pari. La notazione (buffa dice Peter) #' è la scorciatoia per “dammi la funzione con quel nome”. Senza #' il Lisp avrebbe considerato evenp come il nome di una variabile e cercato quella invece della funzione.

Alla remove-if-not si può passare anche una funzione anonima; supponiamo che non ci sia evenp l’espressione precedente può essere riscritta così:

db3

In questo caso la funzione anonima è:

(lambda (x) (= 0 (mod x 2)))

che testa se il numero modulo 2 è 0, cioè pari; per estrarre i dispari si userebbe:

(lambda (x) (= 1 (mod x 2))).

Notare che lambda non è il nome di una funzione ma l’indicatore che sto definendo una funzione anonima. Peraltro l’espressione di lambda è simile a quella di defun: manca il nome della funzione ma c’è la lista dei parametri seguita dal corpo della funzione.

Tornando al DB per estrarre i record relativi alle Dixie Chicks serve una funzione che ritorni true per i record in cui il campo :artist è Dixie Chicks. Avendo costruito il DB con plist possiamo usare getf con l’espressione (getf cd :artist) e per estrarre quelli buoni usare equal: (equal (getf cd :artist) "Dixie Chicks"). Impacchettiamo tutto in un’espressione lambda che passiamo alla remove-if-not:

db4

OK, generalizzando per un qualunque artista si può scrivere:

db5

Ma potrei voler selezionare per gli altri campi e creare select-by-title, select-by-rating, select-by-title-and-artist e via dicendo. Sempre usando la stessa funzione anonima. Invece si può creare usa funzione di selezione più generale che prende una funzione come un argomento.

db6

Cos’è successo a #'? Ecco, in questo caso non voglio che remove-if-not usi la funzione selector-fn ma una funzione anonima che viene passata attraverso la variabile selector-fn. In ogni caso #' ritorna nella call a select:

db7

ma è brutta, fortunatamente si può ricorrere a una funzione anonima:

db8

questa è una funzione che ritorna una funzione e che si riferisce a una variabile che sembra non esistere dopo che artist-selector ritorna. Questa è chiamata closure perché “closes over” (chiude sulla) la variabile, vedremo prossimamente.
Sembra una cosa strana (d’ho!) ma funziona proprio come dovrebbe: se chiamo artist-selector con “Dixie Chicks” come argomento ottengo una funzione anonima che ritorna i CD in cui :artist è “Dixie Chicks” e ottengo un altra funzione chiamandola con “Lyle Lovett”. Posso così scrivere:

db80

Adesso basterebbero solo un pakko di funzioni per generare i selettori. Ma non voglio creare select-by-title, select-by-rating e così via, perché sembrerebbe Basic o PHP 😀
Sarebbe bello creare una funzione selettore general-purpose che genera la funzione selettore in funzione degli argomenti passati.
OK, si può. ma prima un rapidissimo corso di una caratteristica del Lisp, i keyword parameters.

Finora nelle funzioni scritte si è specificato una semplice lista di parametri, collegati ai corrispondenti argomenti nella chiamata alla funzione. per esempio la seguente funzione:

(defun foo (a b c) (list a b c))

ha 3 parametri —a, b e c— e deve essere chiamata con 3 argomenti. Ma a volte si vuole scrivere una funzione che può essere chiamata con un numero variabile di argomenti. I keyword paramters sono un modo per far ciò. La nostra funzione che li usa diventa:

(defun foo (&key a b c) (list a b c))

L’unica differenza è &key all’inizio della lista degli argomenti. Ma il risultato è diverso:

db9

(Nota: OK, sì, c’è ormai da parecchie parti, anche nel Basic).

Come si vede dagli esempi i valori delle variabili a, b e c sono collegati ai valori che seguono le corrispondenti keyword. Per le keyword non presenti la variabile è posta uguale a nil. Ma se si vuole si può fare in modo che il valore della variabile per la keyword mancante abbia un valore diverso, di default. Si ottiene questo sostituendo la lista dei parametri con una lista consistente  nel nome del parametro, un valore di default e un altro parametro chiamato parametro supplied-p. Il parametro supplied-p viene posto vero o falso a seconda di come viene fatta la chiamata:

db10

Il generatore generale di selector-function, una roba da SQL, è una funzione che ha 4 keyword parameters corrispondenti ai campi del nostro DB di CD e genera una funzione selettore che seleziona i CD che soddisfano tutti i valori dati in where. Per esempio si potrà scrivere:

(select (where :artist "Dixie Chicks"))

o, anche:

(select (where :rating 10 :ripped nil))

La funzione è questa:

db11

Questa funzione (where) ritorna una funzione anonima che ritorna l’and per tutti i campi per ogni cd in cui la condizione è soddisfatta. Quindi la funzione selettore ritorna t solo se tutti gli argomenti passati sono soddisfatti.

Parentesi (di Peter): if in Lisp è una forma che ritorna qualcosa, è simile all’espressione C some_var = some_boolean ? value1 : value2; mentre è diverso da some_var = if (some_boolean) value1; else value2; perché nel C (e affini) if è un’istruzione (statement) e non un ‘espressione.

notare che per la keyword parameter ripped si devono passare 3 parametri perché si occorre sapere se la chiamata con :ripped nil significhi “seleziona i CD in cui il campo ripped è nil” oppure, quando non specificato stia per “non m’interessa il campo ripped per il CD”.

Pausa? La strada, dice Peter, è ancora lunga, mi sembra di aver accumulato tanto oggi. 🙄
E poi tutta questa musica, non so voi ma a me fa venire in mente questo:

Julien Neel —Trudbol— sì che saprebbe lispare come si deve 😀

Posta un commento o usa questo indirizzo per il trackback.

Commenti

  • Num3ri  Il 26 febbraio 2015 alle 18:48

    Effettivamente, non posso che darti ragione!
    A me era venuto in mente…..

    “Yankee Doodle went to town
    A-riding on a pony,
    Stuck a feather in his cap
    And called it macaroni.” …

    😉

    • juhan  Il 26 febbraio 2015 alle 19:05

      E sì, il Lisp è diverso. Proposta: vuoi fare un post sull’argomento (sull’altro blog, il Tamburo Riparato)? Assumiamo giovani di buona volontà, sempre.

      • Num3ri  Il 26 febbraio 2015 alle 19:07

        se avessi il tempo, volentieri 🙂

      • juhan  Il 26 febbraio 2015 alle 19:09

        Facciamo che io l’invito te lo mando, senza impegni da parte tua, chissà 😉

Trackback

  • Lisp – un DB semplice – 4 | Ok, panico su 3 marzo 2015 alle 09:45

    […] Continuo da qui, sempre seguendo l’ottimo Peter Seibel. Avendo creato le funzioni select e where si può procedere all’aggiornamento del DB, facendo come in SQL. Salta fuori che la funzione update è solo un’applicazione di cose già viste, usare una funzione selettore per scegliere i record da aggiornare e usare keywords per specificare i valori da cambiare. […]

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: