Author Archives: juhan

Lisp – collezioni – 2

l8Continuando a copiare :wink:

Funzioni speciali: -if e -if-not

Il titolo originale è diverso –Higher-Order Function Variants– ma in italiano suona strano, troppo pretenzioso, secondo me.

Per ognuna delle funzioni discusse nel post precedente il Lisp fornisce una variante high-order, al posto dell’argomento invece dell’elemento metto una funzione da chiamare per ogni elemento della sequenza. Un set di varianti hanno lo stesso nome delle funzioni basi con -if aggiunto. Queste funzioni, count, find, remove e substitute agiscono sugli elementi per i quali gli argomenti della funzione ritorna vero. L’altro set ha il suffisso -if-not e ritorna per i non veri.

c11-10

Secondo lo standard del linguaggio le -if-not sono deprecate, forse sarà eliminata la deprecazione, non le funzioni. Per esempio remove-if-not è più usata di remove-if.

Le -if e -if-not accettano le stesse keyword arguments delle versioni base eccetto :test che non è necessario. Con un argomento :key il valore estratto dalla funzione :key è passato alla funzione invece dell’elemento.

c11-11

La famiglia di funzioni remove ha anche la variante remove-duplicates che ha solo un argomento richiesto, una sequenza, da cui rimuove tutti tranne uno degli argomenti duplicati. Prende gli stessi argomenti di remove eccetto :count, inutile.

c11-12

Manipolazioni dell’intera sequenza

Una manciata di funzioni esegue operazioni su tutta una sequenza, o più sequenze tutto in una volta. Queste tendono a essere più semplici di quelle descritte finora, p.es. copy-seq e reverse entrambe prendono un solo argomento, una sequenza, e ritornano una nuova sequenza dello stesso tipo. La sequenza di copy-seq contiene gli stessi elementi dei suoi argomenti cosa che fa anche reverse ma in ordine inverso. Nessuna delle due copia gli elementi, ritornano solo la sequenza come nuovo oggetto.
La funzione concatenate crea una nuova sequenza con la concatenazione di ogni sequenza. Tuttavia, a differenza di copy-seq e reverse che ritornano lo stesso tipo di sequenza a concatenate bisogna dirgli espressamente che tipo produrre nel caso i suoi argomenti siano di tipo diverso. I suoi primi argomenti sono un descrittore di tipo come :element-type di make-array. in questo caso i descrittori più usai sono vector, list o string. Esempi:

c11-13

Sort e merge

“Ordinare e fondere” suona strano, secondo me , sto invecchiando :evil:
Le funzioni sort e stable-sort permettono di ordinare una sequenza. Prendono una sequenza e un predicato con due argomenti e ritornano la versione ordinata delle sequenza:

c11-14

la differenza tra le due è che stable-sort garantisce di non riordinare gli elementi considerati equivalenti.

Queste funzioni sono esempi di funzioni distruttive (destructive functions). Le funzioni distruttive sono permesse, tipicamente per ragioni di efficienza, di modificare i loro argomenti in modo più o meno arbitrario. Questo ha due implicazioni: puoi usare il risultato della funzione e se vuoi tenere la versione originale devi passare una copia alla funzione. Ovvio, ma prossimamente molto di più sulle funzioni distruttive.

Tipicamente non serve tenere la sequenza non ordinata ma bisogna ricordarsi di scrivere:

(setf my-sequence (sort my-sequence #'string<))

invece di (sort my-sequence #'string<).

Entrambe le funzioni possono prendere la keyword :key usabile per estrarre i valori da passare al predicato di sort al posto degli elementi presenti. le keys estratte sono usate solo per determinare l’ordinazione degli elementi; la sequenza ritornata conterrà gli stessi elementi di quella passata.

La funzione merge prende due sequenze e un predicato e ritorna la sequenza prodotta dalla fusione delle due sequenze in accordo con il predicato, può avere un argomento :key e come concatenate il suo primo argomento dev’essere un descrittore di tipo che specifichi la sequenza da produrre:

c11-15

Manipolazione di sotto-sequenze

Un altro set di funzioni consente di manipolare sotto-sequenza. La più nota delle quali è subseq che estrae una sotto-sequenza a partire da un indice e continuando fino a un indice di fine o alla fine della sequenza:

c11-16

Subseq è anche setf-abile ma questo non si estende alla sequenza ridotta; se il nuovo valore e la sotto-sequenza che dev’essere rimpiazzata hanno lunghezze diverse la più corta determina quanti caratteri sono cambiati:

c11-17

Uh! nel primo caso la sotto-sequenza e il nuovo valore hanno la stessa lunghezza, nel secondo il nuovo valore è troppo lungo e la parte di troppo viene ignorata, nel terzo il nuovo valore è troppo corto e due soli sono cambiati.

Si può usare la funzione fill per settare più elementi con un unico valore. gli argomenti richiesti sono la sequenza e il valore con cui riempirla. Per default ogni elemento della sequenza viene settato ma si possono usare le solite :start e :end.

Per cercare una sotto-sequenza si può usare search che funziona come position eccetto che il primo argomento è una sequenza e non un solo elemento:

c11-18

Se si vuol vedere quando le sequenze cominciano a differire si può usare mismatch che prende due sequenze e ritorna l’indice della prima coppia di argomenti che non sono uguali:

c11-19

Se ritorna nil le due sequenze sono uguali. mismatch può avere keyword arguments: :key  per specificare una funzione da usare per estrarre i valori da comparare; :test per specificare la funzione di comparazione; e :start1, :end1, :start2 e :end2 per definire sotto-sequenze delle sequenze. Se c’è :from-end con valore t le sequenze devono essere trattate in ordine inverso, causando mismatch a ritornare l’indice nella prima sequenza dove le due cominciano a essere uguali:

c11-20

Predicati di sequenze

Quatto funzioni, every, some, notany e notevery testano le sequenze ritornando un valore booleano. Il primo argomento di queste funzioni è il predicato, gli altri sono sequenze. il predicato deve avere tanti argomenti quante sono le sequenze. every termina ritornando falso appena il predicato fallisce, some ritorna il primo non-nil ritornato dal predicato e ritorna falso se mai verificato, notany ritorna falso appena il predicato è soddisfatto e vero se non lo è mai, notevery ritorna vero appena il predicato fallisce e falso se è sempre soddisfatto. Esempi:

c11-21

comparazione di coppie di sequenze:

c11-22

Funzioni di mappatura di sequenze

map, come le funzioni predicato, prende n funzioni argomento e n sequenze ma invece di un booleano ritorna la sequenza contenente il risultato dell’applicazione della funzione ai susseguenti elementi delle sequenze. Come concatenate e merge è necessario specificare che tipo di sequenza deve creare:

c11-23

map-into è come map ma invece di produrre una nuova sequenza rimpiazza il risultato in una sequenza passata come primo argomento. Questa può essere la stessa di una delle sequenze date per ottenere i valori per questa funzione. Per esempio per sommare i vettori a, b e c in a si può scrivere:

(map-into a #'+ a b c)

Se il numero di elementi è diverso per le varie sequenze viene preso solo quello minimo. Tuttavia, se la sequenza che si vuole mappare è un vettore con un fill pointer il numero di elementi è limitato dal fill pointer. Dopo la chiamata il fill pointer risulterà settato al numero di elementi mappati. map-into, tuttavia, non estende un vettore estensibile.

reduce fa un altro tipo di mappatura: mappa una singola sequenza applicando una funzione ai primi 2 argomenti della sequenza e poi il valore restituito agli elementi successivi della sequenza:

c11-24

reduce è una funzione sorprendentemente utile tutte le volte che si vuole ridurre una sequenza a un solo valore. Per esempio per trovare il massimo si può usare (reduce #'max numbers).
reduce può avere i soliti :key, :from-end, :start e :end. In più c’è :initial-value che specifica un valore che è logicamente posto prima del primo elemento della sequenza, o alla fine se con :from-end.

Basta! :mrgreen: fino a domani :wink:

Un paio di segnalazioni lispiche davvero über

uslrIo adesso non vorrei dare l’impressione di essere monomaniaco, per niente.
Ma forse qualcuno, non a torto, potrebbe cominciare a pensarlo. Sbagliando, forse –o forse no, chissà :roll:

Questo preambolo va inteso a (parziale) giustificazione di questo post che è solo una segnalazione di una recente scoperta. Sì, lo so, magari lo sanno tutti ma io non lo sapevo e metti che non lo sapete neanche voi. Se poi non interessa non leggetelo, nèh! (questo vale sempre).

Allora da quando i lispers hanno invaso la mia lista dei seguiti su Twitter saltano fuori tweet meravigliosi. Come quello da cui ho preso l’immagine qui sopra.

Ben diverso questo: The famous 1974 MACLISP “Moonual”

Bello, un gioiello!
1974, io ancora non avevo scoperto i ‘puters (ma stavo per). E il Lisp l’ho incontrato molto dopo ma in una versione molto simile a quella descritta in questo manuale. Purtroppo non ho più la documentazione, non ho trovato niente nel web riguardo al Lisp del Pr1me, forse era una cosa in beta o simile e io l’avevo avuto solo ahumm-ahumm.
Ma siamo lì, l’area di Boston, Multics (Primos ne era un discendente) e il Runoff. Tutte cose che David A. Moon cita nei ringraziamenti, all’inizio del testo (nel PDF sono a p.4).

Non l’ho letto tutto, lo farò quando sarò vecchio, ma una scorsa l’ho data. E ho trovato parecchi indizi. Per esempio mancano ancora le macro, proposte da Timothy Hart proprio in quel periodo (ma non c’erano –probabilmente– nella versione che usavo qualche anno dopo). Ci sono parecchie cose che invece sono diventate standard nella versione standardizzata, Common Lisp. E ci sono nomi che ritornano, in realtà sempre gli stessi, la comunità Lisp era piccola.

Insomma dovevo proprio dirvelo, ecco.

Sempre dalla stessa congrega un altro tweet che sarebbe tutto un programma: The new Common Lisp based #Lisp OS.
Ecco, però no, troppe cose. Sarà per un altra vita. Forse :grin:

Lisp – collezioni – 1

l7Un argomento che è esso stesso una collezione; ma seguo Peter Seibel :grin:
Come tutti anche il Lisp ha un suo modo di collezionare più valori in un unico oggetto. E come capita spesso le cose non sono sempre fatte allo stesso modo. Quelli standard sono arrays, liste e tuple, poi ci sono hash tables, arrays associativi, mappe e dizionari.

Il Lisp è famoso per le sue liste, ma non ci sono solo loro. E siccome sono comode vengono spesso usate dove sarebbe meglio usare arrays e hash tables, nèh! :wink:

Adesso –dice Peter– ci concentriamo su vettori e hash tables; ma i vettori condividono parecchie caratteristiche con le liste, anzi sono entrambe sequenze. Per cui ne parleremo prossimamente.

Vettori

I vettori (arrays monodimensionali; una volta gli arrays da noi fortranisti erano chiamati matrici, non so se è ancora valido o sono finiti nel dimenticatoio come giumpare) sono la collezione base di interi indicizzati e possono essere a dimensione fissa o ridimensionabili.

Per creare un vettore fixed-size si usa vector:

(vector)     ==> #()
(vector 1)   ==> #(1)
(vector 1 2) ==> #(1 2)

La sintassi #(...) è quella standard usata da print e read. Si può usare ma non esiste un modo standard per modificarli e allora meglio make-array per creare vettori che dovranno essere modificati.

make-array è molto più generale, permette di creare arrays multidimensionali,. di dimensioni fisse o variabili. Il solo argomento richiesto è una lista contenente le dimensioni dell’array. Siccome il vettore è monodimensionale questa lista conterrà un solo numero, la dimensione del vettore. In questo caso make-array accetta un numero invece della lista.
Senza altri argomenti make-array crea il vettore da inizializzare prima di potervi accedere. per creare un vettore con gli elementi predefiniti a un certo valore si passa :initial-element. Ecco come creare un vettore di 5 elementi inizializzati a nil:

c11-0

make-array è anche la funzione per creare vettori ridimensionabili. In questo caso il vettore deve contenere anche il suo numero di elementi, nel fill pointer, così chiamato perché è l’indice del prossimo dato da inserire. Ecco come creare un vettore di 5 elementi, vuoto:

c11-1

per aggiungere elementi a questo vettore si usa vector-push-extend che funziona come vector-push ma espande automaticamente il vettore se necessario. Ragion per cui :fill-pointer e :adjustable possono essere omessi se pasticci.

Sottotipi di vettori

I vettori visti finora sono generali, nel senso che possono contenere qualunque tipo di oggetti. È possibile creare vettori specializzati che possono contenere solo certi tipi di oggetti. Sono più compatti e veloci, vediamone un paio.

Uno di questi l’abbiamo già incontrato, la stringa, che contiene caratteri. le stringhe sono così importanti da avere una loro sintassi (le virgolette) e funzioni loro specifiche, già viste. Ma ci sono funzioni per i vettori che valgono anche per loro, come stiamo per vedere.

Le stringhe letterali come “foo” sono come vettori letterali scritti con la sintassi #(), la loro dimensione è fissa e non possono essere mutate. Tuttavia posso usare make-array per creare stringhe ridimensionabili aggiungendo la keyword :element-type. Questa keyword prende un argomento descrittivo, ce ne sono millantamila, a noi serve solo sapere che per le stringhe il simbolo è character; il simbolo dev’essere quotato altrimenti viene considerato una variabile. Esempio di stringa ridimensionabile vuota:

c11-2

Ci sono anche i bit-vectors, vettori i cui elementi possono essere solo zero e uno; hanno un trattamento speciale, si leggono/scrivono come #*00001111 e ci sono intere librerie per trattarli. Il tipo di descrittore da passare con :element-type è –indovina?bit.

Vettori come sequenze

Come già detto vettori e liste sono sequenze; le funzioni seguenti sono funzioni per le sequenze e quindi possono essere usate con loro.

length ritorna la lunghezza di una sequenza; per i vettori con un fill pointer ritorna questo valore, altrimenti il numero di caratteri che contiene (mi sembrano coincidenti, devo pensarci su).
elt (da element) permette di accedere a un elemento individuale della sequenza attraverso in indice intero compreso tra 0 (incluso) e la lunghezza della sequenza (escluso) e ritorna l’elemento corrispondente. Segnala un errore se sei fuori. Esempio:

c11-3

elt permette anche di settare sul posto, esempio:

c11-4

Funzioni per iterare le sequenze

Oltre a length, elt e il setf di elt ci sono parecchie funzioni per le sequenze. Un gruppo di queste consente certe operazioni di ricerca e filtro di elementi senza uno scrivere un specifico ciclo, eccone un riassunto:

Name         Required Arguments             Returns
count        Item and sequence              Number of times item appears in sequence
find         Item and sequence              Item or nil
position     Item and sequence              Index into sequence or nil
remove       Item and sequence              Sequence with instances of item removed
substitute   New item, item, and sequence   Sequence with instances of item replaced 
                                            with new item

Uh! qualche esempio:

c11-5

Notare che remove e substitute ritornano sempre la sequenza del tipo passato.

Si può modificare il comportamento di queste funzioni in parecchi modi con keyword arguments. Per esempio si può usare :test per passare una funzione che accetta 2 argomenti e ritorna un booleano; se fornita verrà usata per comparare item per ogni elemento invece del test di eguaglianza standard eql. Con :key posso passare una funzione con un argomento da chiamarsi per ogni elemento della sequenza per estrarre una key che dev’essere comparata con l’elemento. Notare tuttavia che find che ritorna elementi della sequenza continua a ritornare l’elemento corrente e non solo la chiave estratta. Capito molto poco, anzi niente, nada, zilch :evil:

c11-6

Per limitare queste funzioni a una sub-sequenza si possono fornire :start e :end. Se :end è nil o omesso viene considerata la lunghezza della sequenza.
Si può passare :from-end per esaminare gli elementi in ordine inverso; funziona solo con find e position:

c11-7

Tuttavia :from-end altera remove e substitute in congiunzione con :count, usato per indicare quanti elementi rimuovere o sostituire:

c11-8

E mentre :from-end non può cambiare il risultato della funzione count altera l’ordine con cui sono passati :test e :key alle funzioni, con possibili effetti collaterali, esempio:

c11-9

Tabella riassuntiva per questi argomenti:

Argument   Meaning                                            Default
:test      Two-argument function used to compare item 
           (or value extracted by :key function) to element.  eql
:key       One-argument function to extract key value from 
           actual sequence element. nil means use element 
           as is.                                             nil
:start     Starting index (inclusive) of subsequence.         0
:end       Ending index (exclusive) of subsequence. 
           NIL indicates end of sequence.                     nil
:from-end  If true, the sequence will be traversed in 
           reverse order, from end to start.                  nil
:count	   Number indicating the number of elements to 
           remove or substitute or nil to indicate all 
           (remove and substitute only).                      nil

Panico? No, dai, continua, prossimamente :mrgreen:

Lisp – numeri, caratteri e stringhe – 2

l6
Continuo a copiare da qui.

Caratteri

I caratteri sono un tipo distinto dai numeri, a differenza di come siamo abituati. Questo potrebbe portare a problemi con Unicode, chissà…
La sintassi per l’oggetto carattere è #\ seguita dal carattere desiderato. Quindi per x sarà #\x e questo vale anche per i caratteri speciali come ", ( e spazi. Ma ci sono anche dei nomi come #\space e #\newline. Altri nomi semi-ufficiali sono Tab, Page, Rubout, Linefeed, Return e Backspace.

Comparazione tra caratteri

Oltre che inserirli nelle stringhe (dopo quando parliamo di quelle) l’altra cosa che si può fare è compararli. Siccome non sono numeri non si possono usare le funzioni numeriche di comparazione. Esistono invece due set di funzioni uno sensitivo (ahemmm, che distingue le maiuscole dalla minuscole) l’altro no.

Il case-sensitive, analogo a = per i numeri, è composto dalle funzioni char= che può prendere un numero qualunque di argomenti e ritorna vero solo se sono tutti lo stesso carattere. La versione case-insensitive è char-equal.

Il resto dei comparatori seguono lo stesso schema. Quelli sensitivi hanno il nome come i numerici con il prefisso char; quelli insensitivi dopo il char hanno un trattino (quello che io continuo a chiamare (segno) meno).

Riassunto:

Numerico     Sensitivo     Insensitivo
=	     char=	   char-equal
/=	     char/=	   char-not-equal
<	     char<	   char-lessp 
>	     char>	   char-greaterp
<=	     char<=	   char-not-greaterp 
>=	     char>=	   char-not-lessp

Ci sono poi altre funzioni per testare se il carattere è alfabetico o numerico, maiuscolo o minuscolo, trasformarlo in numero o viceversa. Al solito consultare la Common Lisp reference preferita. Io uso questa veloce ma ce ne sono altre, per esempio LispWorks.

Stringhe

Le stringhe in Common Lisp sono un tipo composto, un array monodimensionale di caratteri. Per cui si parlerà di loro –dice Peter– quando si tratteranno le sequenze, di cui le stringhe sono un tipo. Ma hanno una loro sintassi letterale e una libreria di funzioni per le loro operazioni specifiche e questi aspetti saranno trattati adesso.

Le stringhe sono scritte racchiuse tra virgolette. Entro le virgolette ci possono essere tutti i caratteri esclusi " e \. Per includere questi due si devono backslashare. Come per altri linguaggi (C, p.es.) il backslash in realtà funziona per qualsiasi carattere che lo segue, anche quando non serve.
Ecco una tabella sulle stringhe letterali:

Letterale	  Contenuto	    Commento
"foobar"	  foobar	    stringa normale
"foo\"bar"	  foo"bar	    il backslash escapa la quote
"foo\\bar"	  foo\bar	    il primo backslash escapa il secondo
"\"foobar\""      "foobar"	    i backslashes escapano le quotes
"foo\bar"	  foobar	    il backslash inutilmente escapa b 

Notare che la REPL scrive le stringhe nel formato leggibile dal reader, quindi racchiudendole tra virgolette e mettendo i backslashes necessari. Per vedere le stringhe “come appariranno” occorre usare format, progettata per scrivere in modo umano. Esempio:

n10-10

Comparazione tra stringhe

Si può usare un set di funzioni con nomi che seguono la stessa convenzione di quelle per i caratteri sostituendo char con string. Ecco la tabella:

Numerico     Caso-Sensitivo   Caso-Insensitivo
=	     string=	      string-equal
/=	     string/=	      string-not-equal
<	     string<	      string-lessp 
>	     string>	      string-greaterp
<=	     string<=	      string-not-greaterp 
>=	     string>=	      string-not-lessp 

Ma, a differenza dei caratteri, queste funzioni possono comparare due stringhe. Questo perché prendono anche keyword arguments che consentono di restringere la comparazione a sottostringhe. Gli argomenti :start1, :end1, start2 e :end2 indicano l’inizio (incluso) e la fine (esclusa) delle sottostringhe. Quindi:

(string= "foobarbaz" "quuxbarfoo" :start1 3 :end1 6 :start2 4 :end2 7)

compara la sottostringa “bar” nei due argomenti e ritorna vero. :end1 e :end2 possono essere nil o omessi per indicare la fine della stringa.

I comparatori string= e string-equal ritornano l’indice del primo carattere che differisce:

n10-11

Se la prima stringa è l’inizio della seconda ritornerà la lunghezza della prima stringa, vale a dire uno più del più grande indice possibile per la stringa:

n10-12

Quando si comparano sottostringhe il valore risultante è ancora un indice della stringa nel suo complesso. L’esempio seguente compara “bar” e “baz”, ritorna 5 che è l’indice di r nella prima stringa:

n10-13

Altre funzioni per le stringhe consentono di convertirle in maiuscolo/minuscolo, cancellare gli spazi a una o entrambe le estremità. Siccome le stringhe sono sequenze tutte le funzioni che verranno trattate per queste ultime possono essere usate per le stringhe, per esempio length per la lunghezza, elt

elt

e aref

aref

Si può poi usare char

char

ma tutto questo prossimamente, forse :mrgreen:

Le origini del Lisp – un doc notevole

lfe-cupCi sono degli argomenti di cui non si può parlare –verboten!– per esempio Excel, Matlab e altri che non posso nemmeno nominare :wink:
Ce ne sono altri di cui mi piacerebbe parlare ma conosco chi saprebbe farlo molto meglio di me, alcuni hanno anche mezzo promesso, se solo trovassero il tempo.
E allora io mi rifugio in qualcosa non troppo sexy ma che mi va, per esempio il Lisp. E si trovano cose molto interessanti (almeno per me, i post sul Lisp hanno pochissime visite), persone eccezionali, come Peter Seibel, guarda qua :grin:
E i lispers stanno aumentando tra quelli che seguo su Twitter. Poi magari ne guarirò, chissà… :roll:
E da un tweet ho scoperto questo post: The Hidden Origins of Lisp: The Place of Lisp in the 21st Century.
In realtà è quintuplice (si può dire, vero?), l’autore racconta brevemente di chi ha ispirato la sua opera. Abbiamo perciò brevi note biografiche di Giuseppe Peano, Bertrand Russell, Alonzo Church e John McCarthy. Interessantissime, ci sono cose che non sapevo. Anche se per Peano vorrei aggiungere una cosa, sapete ho fatto il Poli da piccolo :wink:

Ma quello che mi piace davvero del post di Duncan McGreggor (ecco chi è l’autore!) è:

The inevitable question is then asked: what use is Lisp, more than 50 years after its creation, when the world of computing – both research and industry – are so vastly different from what they were in Lisp’s early days?
The first answer usually given is one that requires very little thought: macros. There are numerous books written on this topic and we will not cover it further in this preface, but accept as a given that the support of Lisp-style macros in any programming language is a powerful tool.
Once we get past the obvious answer, subtler ones come to the fore. For instance, the simplicity of the syntax and similarity to parenthetically grouped algebra expressions make for an easy programming introduction to students of a middle school age. This simplicity is also something offering great insights to experienced programmers. Alan Kay’s famous quote of Lisp being the software equivalent of Maxwell’s famous partial differential equations for classical electrodynamics derives its inspiration from this simplicity: one can fit the essence of the language in one’s head or on a single sheet of paper.

Ecco. Mi sa che non guarirò tanto presto dalla lispite. Quando finirò il Practical Common Lisp ci sarà –probabilmente, tenete conto che per le previsioni riguardanti il futuro bisogna sempre seguire Niels– il SICP nella versione LFE.

Visto che sono in argomento: vero che Peter a volte è elementare ma non è una pecca, anzi. Il Lisp è diverso dai linguaggi di programmazione prevalenti e ha i suoi quirk, occorre conoscerli. E poi è anche divertente. Almeno per me.
sicp

Visto nel Web – 176

Nonostante l’ora legale puntuale come sempre ecco una nuovaa puntata di cosa ho visto nel Web.
524908_518201738225362_329921204_n
Ostajan joskus vaikea tietää mitä saa. 100% turvallisuuttahan ei ole
::: naranek

7zx
7zx is a small C library to extract, test and list 7z / 7zip archives
::: eXpl0it3r

Using Hy, a dialect of Lisp for Python, with QGIS
::: Nathan Woodrow

Ten years of Kubuntu
::: LWN

A little black box makes it easy to unlock almost any iPhone even when it’s secured with your fingerprint
::: Business Insider

Monday

The fight for the open internet isn’t over
::: Engadget

Starbucks e il suo manager che “sbrocca” su Twitter
::: Tech Economy

Sorveglianza digitale e lotta al terrorismo: perché la nuova legge francese non è il modello da imitare
::: Wired

Windows 10 might not peacefully coexist with other OS
::: SlashGear

850 Sports News Digest for iOS and Android brings you global sports updates in 30 words or less
il mondo cambierà ancora
::: The Next Web

ByfLkAKCAAEuGT-

Hack del BIOS, ricercatori trovano metodo per attaccare tutti i PC
::: Downloadblog

Brace yourself, debug is coming
::: CommitStrip

Pixar Releases Free Version of RenderMan
::: Slashdot

For the first time ever, the NFL will broadcast a game only on the Web
::: The Next Web

Snowden should be allowed a public interest defense, say European lawmakers
::: Ars Technica

aArRvmd_700b

Facebook come piattaforma editoriale, dice New York Times
::: Luca De Biase

Via @xach
mah…
::: peterseibel

Apple Italia, un’organizzazione occulta che fattura oltre un miliardo di euro
è un po’ come una chiesa
::: la Repubblica ::: RaiNews24 ::: Corriere della Sera

Want to learn how #Antenna Toolbox makes antenna and array design in #MATLAB easy?
::: MATLAB

10402997_941768699175439_2074718824908301369_n

# /etc/init.d/daemon stop
::: Manz

Indian Supreme Court Strikes Down Law Against Posting ‘Offensive’ Content Online
::: Slashdot

Tech Bubble? Maybe, Maybe Not
::: TechCrunch

Una svista rilevante nel provvedimento antiterrorismo
::: Stefano Quintarelli ::: Gazzetta di Mantova ::: Signor D ::: manteblog ::: fabiochiusi ::: fabiochiusi ::: TgLa7 ::: fabiochiusi ::: filippobubbico ::: marcobellezza ::: la Repubblica ::: RaiNews24 ::: SIAMO GEEK

10997827_627289200704079_2827663366111520104_n

Google visited the White House once a week
::: ranimolla

Colossi web e attivisti per la privacy scrivono al Congresso: “è tempo di riformare la sorveglianza NSA”
::: Chiusi nella rete

Perchè usare LibreOffice
::: Dario Cavedon (iced)

Dart for the Entire Web
::: Dart News & Updates

How open source can improve your software’s security
::: TechRepublic

11071588_10206264735206784_4602760267743573442_n

The number of WhatsApp messages sent every day now exceeds the number of standard texts
::: TheEconomist

Guide Lubit
::: the secrets of ubuntu

Facebook has officially declared it wants to own every single thing you do on the internet
diventerà per tutti quello che adesso è solo per qualcuno: FB == Internet
::: The Next Web

The plot to replace the internet
qualcuno ne sa qualcosa?
::: The Spectator

A map of all the underwater cables that connect the internet
::: Vox

ask for x

The Sunset of C and C++
::: biicode

Periscope and live video are changing the internet forever
::: The Next Web

GNOME 3.16 Released
::: GNOME

Basic Internet Security
::: FLOSS Manuals

Facebook’s Aquila Drone Will Beam Down Internet Access With Lasers
::: TechCrunch

10988475_928390327193979_8576120988721361975_n

One Professional Russian Troll Tells All
fonte dubbia, chissà…
::: Radio Free Europe/Radio Liberty

Learn Python The Hard Way
::: Learn Python The Hard Way

This Newsletter Was Paranoid About the NSA in 1996, and It Was Eerily Correct
::: Motherboard

gtkdialog
A small utility for fast and easy GUI building
::: Google code

rms.001

Rebuilding the PDP-8 With a Raspberry Pi
::: Slashdot

Hoax-Detecting Software Spots Fake Papers
::: Slashdot

Una giornata su Periscope
::: Wired

tech

Europe vs Facebook: il Datagate alla Corte di Giustizia Europea
::: Valigia Blu

IBM and OpenPower Could Mean a Fight With Intel For Chinese Server Market
::: Slashdot

European Commission Will Increase Use of Open Source Software
::: Slashdot

tumblr_nksg7s5vAr1qa9m0zo1_1280

Lisp – numeri, caratteri e stringhe – 1

l5Mentre funzioni, variabili, macros e 25 operatori speciali forniscono componenti base del linguaggio stesso i componenti base dei nostri programmi saranno le strutture di dati che usiamo. Proprio come dice Fred Brooks in The Mythical Man-Month, “Representation is the essence of programming”.
La prima versione è disponibile ontehtoobz :grin:

Così parte un nuovo capitolo del bravissimissimo Peter.
Il Common Lisp fornisce il supporto built-in per i tipi di dati più comuni:

  • numeri (interi, floating point e complessi);
  • caratteri;
  • stringhe;
  • arrays (mono- e multidimensionali);
  • liste;
  • hash tables;
  • flussi (streams) di input e output;
  • un astrazione per rappresentare i nomi dei files.

Inoltre le funzioni sono un tipo first-class, possono essere memorizzate in variabili, passate come argomento, ritornate come valore e create a runtime.

Questi tipi built-in sono solo l’inizio; sono definiti nel linguaggio e il programmatore può contare su di essi ma come vedremo in futuro si possono definire altri tipi e operazioni su di essi per integrare i presenti.
Dal punto di vista del programmatore i vari tipi sono definiti dalle funzioni che operano su di essi, per cui per impararne uno consiste nel vedere che funzioni si possono usare con quello. Inoltre diversi tipi hanno una sintassi base che il reader conosce e che il printer usa. Per questo motivo si può scrivere stringhe come "foo" e numeri come 123, 1/23 e 1.23 e liste come (a b c). La sintassi per i vari tipi sarà descritta quando saranno descritte le funzioni che operano su di loro.

Per adesso solo i tipi di dati scalari: numeri, caratteri e stringhe.
Tecnicamente le stringhe non sarebbero uno scalare ma una sequenza di caratteri ma la maggior parte delle funzioni specifiche che manipolano le stringhe come singolo valore e per la stretta relazione tra parecchie funzioni riguardanti le stringhe e le loro corrispondenti per i caratteri.

Numeri

La matematica è difficile (cit.). E Common Lisp non la rende più facile di altri linguaggi, anzi. Ci sono ragioni storiche: il Lisp è stato progettato da matematici per studiare funzioni matematiche. E uno dei principali progetti di MAC (antenato di Common Lisp al MIT) era il sistema di algebra simbolica Macsyma. E il Lisp era usato per insegnare a usare computers e i prof rabbrividivano a dire agli studenti che 10/4 = 2, cosa che ha portato al supporto dei numeri razionali. A differenza del Fortran, p.es. :wink:

Uno dei motivi per cui il Lisp è buono per operazioni matematiche è il suo modo di approssimare; per esempio gli interi possono essere praticamente arbitrariamente grandi, limitati solo dalle caratteristiche della word sulla macchina. Ci sono dei limiti ma “these limits are going to be well beyond “astronomically” large numbers. For instance, the number of atoms in the universe is estimated to be less than 2^269; current Common Lisp implementations can easily handle numbers up to and beyond 2^262144“.
E la divisione tra due interi restituisce la frazione (ratio), non un valore troncato. E siccome il ratio rappresenta una coppia di interi la frazione ha precisione arbitraria. Questo vuol dire che le operazioni matematiche con il Lisp sono più lente che non con altri linguaggi (Fortran, C/C++) a meno di ricorrere a tecniche particolari, non trattate dal Peter.
Ma, non buttiamoci giù, funziona –davvero.
Infine il Common Lisp implementa i numeri complessi, non ci spaventano mica il logaritmo o la radice di un numero negativo. :grin:

Numeri letterali

I numeri possono essere scritti in diversi modi:

n10-0

Possono essere preceduti dal segno (+ o -). I rapporti (ratios) hanno numeratore e denominatore separati da /, senza spazi e vengono canonizzati per cui 10 e 20/2 sono lo stesso numero, come pure 3/4 e 6/8.
Si possono usare anche altre basi. #B e #b sono per i numeri binari, #O e #o per gli ottali, #X e #x per gli esadecimali. Si può usare la forma #nR dove n, scritto in decimale, è compreso tra 2 e 36 o, se maggiore di 9 con le lettere A-Z (e a-z). Si possono scrivere anche interi (non ratios) con un punto decimale finale. Volendo si può anche cambiare la base di default cambiando la variabile *read-base*, however, it’s not clear that’s the path to anything other than complete insanity.

Alcuni esempi di interi e razionali:

n10-1

Anche i numeri floating-point possono essere scritti in diversi modi. A differenza dei numeri razionali il modo con cui vengono scritti può influenzarne il tipo. Common Lisp definisce 4 sottotipi: short, single, double e long. Dipendono dall’hardware.

La forma base è quella di un segno opzionale seguito da una sequenza non vuota di cifre con un possibile punto decimale nel mezzo. La sequenza può essere seguita da un esponente costituito da una singola lettera seguita da un segno opzionale e da una sequenza di cifre. La lettera oltre che indicare che c’è l’esponente ne definisce anche il sotto-tipo; può essere s, f, d, l (e maiuscole corrispondenti) per short, single, double e long. La lettera stabilisce il tipo di rappresentazione iniziale.

Numeri senza esponente devono avere un punto decimale e almeno una cifra dopo per distinguerli dagli interi. Sono sempre base 10. Esempi:

n10-2

Infine i numeri complessi sono scritti con #C o #c seguita da una lista di due numeri reali rappresentanti la parte reale e immaginaria del numero. Ci sono 5 tipi di complessi perché la parte reale e quella immaginaria possono essere razionali o dello stesso tipo di floating-point.

Ma si possono scrivere come si vuole, c’è una conversione di tipo (cast del C) a quella appropriata, con upgrade.
Tuttavia se la parte immaginaria è per un razionale e zero sono convertiti a un tipo non complesso. Per i floating-point no, restano complessi anche con la parte immaginaria nulla.
Esempi:

n10-3

Matematica elementare

le operazioni aritmetiche sono supportate da tutti i tipi con le funzioni +, -, * e /. Chiamando queste funzioni con più di due argomenti equivale a chiamarle con i primi due e successivamente tutti gli altri, per esempio (+ 1 2 3) è equivalente a (+ (+ 1 2) 3). Con un solo argomento + e * ne restituiscono il valore, - la negazione e / il reciproco.
Esempi:

n10-4

Se tutti gli argomenti sono dello stesso tipo il risultato è di quel tipo tranne per razionali e complessi con la parte immaginaria nulla. Per i float c’è la conversione come per il C, esempi:

n10-5

Siccome / non tronca ci sono 4 tipi di arrotondamenti per convertire un numero razionale o float in un intero:

  • floor tronca verso infinito negativo, ritornando il più grande intero minore o uguale all’argomento;
  • ceiling tronca verso infinito positivo, ritornando il più piccolo intero maggiore o uguale all’argomento;
  • truncate tronca verso 0, simile a floor per i numeri positivi e a ceiling per i negativi;
  • round arrotonda all’intero più vicino. Se è esattamente a metà arrotonda al numero pari.

Ci sono poi due funzioni correlate mod e rem, che ritornano il modulo e il resto troncando la divisione dei numeri reali. Queste funzioni sono correlate a foor e truncate come segue:

(+ (* (floor    (/ x y)) y) (mod x y)) === x
(+ (* (truncate (/ x y)) y) (rem x y)) === x

Quindi per rapporti positivi sono equivalenti ma per i negativi producono risultati diversi.

Le funzioni 1+ e 1- forniscono una scorciatoia per aggiungere e sottrarre 1 a un numero. Notare che sono diverse dalle macro incf e decf perché ritornano un nuovo valore mente incf e decf cambiano il valore in place. Le equivalenze seguenti visualizzano le relazioni tra incf/decf, 1+/1- e +/-:

(+ (* (floor    (/ x y)) y) (mod x y)) === x
(+ (* (truncate (/ x y)) y) (rem x y)) === x
(incf x 10) === (setf x (+ x 10))
(decf x 10) === (setf x (- x 10))

Comparazioni tra numeri

La funzione = è il predicato numerico di uguaglianza. Compare i numeri per il loro valore matematico ignorando la differenza di tipo, a differenza di eql che considera il tipo. equalp usa =. Se chiamata con più di due argomenti considera solo quelli con lo stesso valore, esempi:

n10-6

La funzione /= ritorna vero solo se sono tutti diversi:

n10-7

Le funzioni <, >, <= e >= ordinano razionali e float. Come per = e /= possono essere chiamate con più di due argomenti, in questo caso ogni argomento è comparato con l’argomento alla sua destra:

n10-8

per selezionare il più piccolo o il più grande di diversi numeri ci sono le funzioni min e max:

n10-9

Ci sono poi zerop, minusp e plusp che testano se il numero è uguale, minore o maggiore di zero. Inoltre evenp e oddp testano se l’intero è pari o dispari.

Matematica superiore

le funzioni viste sono solo l’inizio di quelle matematiche built-in. Ci sono: log, logaritmo, exp e expt per l’esponenziale, sin, cos e tan per le trigonometriche e le loro inverse asin, acos e atan, le iperboliche sinh, cosh e tanh e le loro inverse asinh, acosh e atanh. Ci sno quelle per estrarre la parte di un ratio o di un complesso. Per la lista completa vedere una Common Lisp reference, io uso Bert Burgemeister, salvato in locale.

Lisp – Costruzione di una semplice unità di test – 2

l4Da finire il thriller iniziato qui, copiando qui.

Visualizzare meglio i risultati

Se si ha solo una funzione da testare l’ultima versione, quella raccontata nel post precedente funziona. Ma se si devono scrivere parecchi test si può migliorare. Per esempio supponiamo di voler testare la funzione *. Si potrebbe pensare di scrivere:

(defun test-* ()
  (check
    (= (* 2 2) 4)
    (= (* 3 5) 15)))

Adesso che si hanno due funzioni di test conviene probabilmente scriverne un’altra che le esegue tutte.

(defun test-arithmetic ()
  (combine-results
   (test-+)
   (test-*)))

Questa funzione deve fare qualcosa così:

CL-USER> (test-arithmetic)
pass ... (= (+ 1 2) 3)
pass ... (= (+ 1 2 3) 6)
pass ... (= (+ -1 -3) -4)
pass ... (= (* 2 2) 4)
pass ... (= (* 3 5) 15)
T

cioè usare combine-results per avere i risultati parziali e globale sia per test-+ che per test-*.
Però se abbiamo diverse funzioni da esaminare ognuna con più casi sarebbe bene che il risultato dei fallimenti fosse più dettagliato.

Siccome il codice che scrive il risultato è centralizzato in report-result bisogna dire a questa che funzione si sta testando. Si potrebbe aggiungere un parametro a report-result ma check che genera le chiamate a report-result non sa da che funzione viene chiamata il che vorrebbe dire cambiare anche il modo con cui si chiama check, passandogli un argomento che passerebbe semplicemente a report-result.

Questo è il tipo di problema per cui esistono le variabili dinamiche. Se si crea una variabile dinamica che ogni funzione da testare collega al nome della funzione prima di chiamare check allora report-result può usarla anche se check non sa nulla a riguardo.

Il primo passo è di dichiarare la variabile al top level:

(defvar *test-name* nil)

Poi occorre modificare report-result per includere *test-name* nel format:

(format t "~:[FAIL~;pass~] ... ~a: ~a~%" result *test-name* form)

Con queste modifiche però la variabile *test-name* non è mai ridefinita e si avrebbe

CL-USER> (test-arithmetic)
pass ... NIL: (= (+ 1 2) 3)
pass ... NIL: (= (+ 1 2 3) 6)
pass ... NIL: (= (+ -1 -3) -4)
pass ... NIL: (= (* 2 2) 4)
pass ... NIL: (= (* 3 5) 15)
T

Occorre cioè inserirla in test-+ e test-* così:

(defun test-+ ()
  (let ((*test-name* 'test-+))
    (check
      (= (+ 1 2) 3)
      (= (+ 1 2 3) 6)
      (= (+ -1 -3) -4))))

(defun test-* ()
  (let ((*test-name* 'test-*))
    (check
      (= (* 2 2) 4)
      (= (* 3 5) 15))))

e quindi ottengo:

t9-5

Emerge un’astrazione

Aggiustando le funzioni di test abbiamo introdotto parecchie duplicazioni. Ogni funzione deve definire il nome 2 volte: in defun e nella variabile *test-name* ma le stesse righe di codice sono duplicate per le due funzioni. Già si potrebbe rimuoverle perché il codice non è bello ma c’è altro, una lezione importante da imparare su come si devono usare le macros, minaccia Peter, nèh, non io :wink:

La ragione per cui partono allo stesso modo è perché sono entrambe funzioni di test. La duplicazione c’è perché la funzione di test è solo la prima metà dell’astrazione che fa dire “questa è una funzione di test” ma il codice non lo esprime.

Le astrazioni parziali sono un povero tool (crummy dice Peter) per sviluppare codice. Intanto ci sono duplicazioni e poi il solito problema della manutenzione, specie con programmatori differenti, o lo stesso in tempi diversi. Insomma per rendere completa l’astrazione occorre che il modo di rendere “questa è una funzione di test” e avere il codice richiesto dal modello serve –indovina?– una macro.

Siccome lo schema che stiamo tentando di fare è di catturare una defun più qualche codice standard abbiamo bisogno di scrivere una macro che venga espansa in una defun. Si userà poi questa macro invece della della defun per definire le funzioni di test, quindi è sensato chiamarla deftest.

(defmacro deftest (name parameters &body body)
  `(defun ,name ,parameters
    (let ((*test-name* ',name))
      ,@body)))

Con questa macro si può riscrivere test-+ in questo modo:

(deftest test-+ ()
  (check
    (= (+ 1 2) 3)
    (= (+ 1 2 3) 6)
    (= (+ -1 -3) -4)))

Una gerarchia di test

Adesso salta fuori una domanda: test-arithmetic dev’essere una funzione di test? per come stanno le cose non ha importanza, se la si definisce con deftest il suo collegamento alla variabile *test-name* verrà showato (nascosto) da quelli collegati a test-+ e test-*.

Ma immaginiamo di avere migliaia di test da organizzare. Il primo livello dell’organizzazione è dato da test-+ e test-* che chiamano check direttamente. Ma con migliaia di casi probabilmente si renderanno necessari altri livelli di organizzazione. Funzioni come test-arithmetic possono raggruppare funzioni simili in pacchetti di test. Supponiamo che per qualche livello basso le funzioni siano chiamate da più pacchetti di test. E può capitare che un test passi in un contesto e fallisca in un altro. Se questo succede probabilmente serve conoscere più del solo nome della funzione di basso livello che contiene il test del caso.

Se si definisce il pacchetto di funzioni di test come test-arithmetic con deftest e si fa un piccolo cambiamento a *test-name* si possono avere i risultati riportati con tutto il percorso, qualcosa come:

pass ... (TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)

Siccome abbiamo già reso astratto il processo di definire le funzioni di test cambiare il risultato dell’output non richiede una modifica alle funzioni di test. Per rendere *test-name* contnente una lista di nomi invece di uno solo è richiesto solo di modificare la sua binding form da

(let ((*test-name* ',name))

a

(let ((*test-name* (append *test-name* (list ',name))))

append ritorna una nuova lista fatta dagli elementi dei sui argomenti. in questo caso aggiunge il nuovo nome alla lista attuale. Appendere con append non è il metodo più efficiente ma dai, in questo caso va più che bene. Quando la funzione di test ritorna il vecchio valore viene ripristinato.

Adesso si può ridefinire test-arithmetic con deftest invece che con defun:

(deftest test-arithmetic ()
  (combine-results
   (test-+)
   (test-*)))

E quando i test aumentano si potrà avere:

(deftest test-math ()
  (test-arithmetic))

Da provare!

t9-6

Conclusione

Questo modello è ampliabile, si possono aggiungere cose ma nella sua versione corrente è tutto qui (ho aggiunto with-gensym e le funzioni attuali di test, cioè tutti quello che serve per lanciare il test dello screenshot precedente):

(defmacro with-gensyms ((&rest names) &body body)
  `(let ,(loop for n in names collect `(,n (gensym)))
     ,@body))

(defvar *test-name* nil)

(defmacro deftest (name parameters &body body)
  "Define a test function. Within a test function we can call
   other test functions or use 'check' to run individual test
   cases."
  `(defun ,name ,parameters
    (let ((*test-name* (append *test-name* (list ',name))))
      ,@body)))

(defmacro check (&body forms)
  "Run each expression in 'forms' as a test case."
  `(combine-results
    ,@(loop for f in forms collect `(report-result ,f ',f))))

(defmacro combine-results (&body forms)
  "Combine the results (as booleans) of evaluating 'forms' in order."
  (with-gensyms (result)
    `(let ((,result t))
      ,@(loop for f in forms collect `(unless ,f (setf ,result nil)))
      ,result)))

(defun report-result (result form)
  "Report the results of a single test case. Called by 'check'."
  (format t "~:[FAIL~;pass~] ... ~a: ~a~%" result *test-name* form)
  result)  

(deftest test-+ ()
    (check
      (= (+ 1 2) 3)
      (= (+ 1 2 3) 6)
      (= (+ -1 -3) -4)))

(deftest test-* ()
    (check
      (= (* 2 2) 4)
      (= (* 3 5) 15)))    
      
(deftest test-arithmetic ()
  (combine-results
   (test-+)
   (test-*)))

E, cito il Peter:

It’s worth reviewing how you got here because it’s illustrative of how programming in Lisp often goes.

Siamo partiti da una semplice versione del problema –come valutare una serie di espressioni booleane simili e verificare che siano tutte verificate. Farne l’and era OK ma è saltata fuori la necessità di un report migliore per cui si è scritto qualche riga di codice super-semplice, pieno di duplicazioni e facilmente soggetto a errori.
La faccio breve, Peter è più didascalico, l’eliminazione delle duplicazioni ha tirato in ballo anche altro, e alla fine si è raggiunto un risultato con poche righe di codice, con un ttima astrazione, ampliabile. E sì, le macros, sempre loro. :mrgreen:

Lisp – Costruzione di una semplice unità di test – 1

l3Come da titolo, seguendo Peter Seibel (che rockz! assay).
Cosa che promette di usare alcune delle cose imparate finora.

Lo scopo principale dell’unità di test sarà di rendere il più possibile facile di aggiungere nuovi test per controllarne varie serie e tener conto di quelli non superati. partiamo con la progettazione di un’unità che si può usare durante lo sviluppo interattivo.

La caratteristica chiave di un’unità di test è che sia lei la responsabile di dirci se tutti i test sono passati. Non vogliamo perder tempo controllando l’output per vedere le risposte quando il computer può farlo più velocemente e accuratamente. Di conseguenza ogni test dev’essere un espressione booleana. per esempio se volessimo scrivere test per la funzione built-in + i casi potrebbero essere:

(= (+ 1 2) 3)
(= (+ 1 2 3) 6)
(= (+ -1 -3) -4)

Le funzioni che hanno side effects (effetti collaterali) devono essere testate in modo differente, verificando anche i previsti side effects. Ma in ogni caso vogliamo un SÌ o NO.

Due primi tentativi

Se si vogliono fare i test precedenti con la REPL basta verificare che ritornino t. Nel nostro caso si può scrivere una funzione che ritorna l’and di tutti i risultati:

t9-0

OK, finché torna t si sa che tutti sono passati. Ed è molto conciso. Ma appena uno fallisce ottieni nil ma non sai per quale caso.
Si potrebbe tentare un’altra via, qualcosa come questo:

t9-1

In questo caso l’output è migliore ma ancora grossolano. C’è la ripetizione di format e la duplicazione dell’espressione che si vuole testare, insomma riscrivere. Inoltre non si ottiene un risultato globale per tutta la serie e quando si hanno tanti test può capitare che non vedi il “FAIL”.

Refactoring

Come si dice da noi? Lascio quello.
Quel che si vuole è un risultato globale (t o nil) ma anche sui singoli test. Siccome la seconda versione è più vicina a quel che si vuole partiamo da quella, evitando le duplicazioni:

(defun report-result (result form)
  (format t "~:[FAIL~;pass~] ... ~a~%" result form))

Questa funzione si può usare dentro la test-+, in questo modo:

(defun test-+ ()
  (report-result (= (+ 1 2) 3) '(= (+ 1 2) 3))
  (report-result (= (+ 1 2 3) 6) '(= (+ 1 2 3) 6))
  (report-result (= (+ -1 -3) -4) '(= (+ -1 -3) -4)))

Adesso vediamo per la duplicazione. Quel che serve è di trattare l’espressione sia come codice (per avere il risultato) che come dati (per la scritta). Ogni volta che si vuole trattare il codice come dati saltano fuori le macros. Quello che vorremmo è qualcosa come:

(check (= (+ 1 2) 3))

con il significato di:

(report-result (= (+ 1 2) 3) '(= (+ 1 2) 3))

Scrivere la macro è banale (dice):

(defmacro check (form)
  `(report-result ,form ',form))

Nota per me: Uh! `|,|', :roll:

Adesso test-+ diventa:

(defun test-+ ()
  (check (= (+ 1 2) 3))
  (check (= (+ 1 2 3) 6))
  (check (= (+ -1 -3) -4)))

Possiamo evitare la ripetizione di tutte queste chiamate a check? Si può ridefinire check in modo da prendere un numero arbitrario di forms e avvolgerle tutte in una chiamata a report-result:

(defmacro check (&body forms)
  `(progn
     ,@(loop for f in forms collect `(report-result ,f ',f))))

progn esegue una serie di forms in sequenza (è il blocco {...} del C). Notare ,@ per connettere in una sola espressione la lista di espressioni generate dal modello backquotato.

con questa nuova versione di check test-+ può essere scritta:

(defun test-+ ()
  (check
    (= (+ 1 2) 3)
    (= (+ 1 2 3) 6)
    (= (+ -1 -3) -4)))

ed è equivalente a:

(defun test-+ ()
  (progn
    (report-result (= (+ 1 2) 3) '(= (+ 1 2) 3))
    (report-result (= (+ 1 2 3) 6) '(= (+ 1 2 3) 6))
    (report-result (= (+ -1 -3) -4) '(= (+ -1 -3) -4))))

grazie a check questa versione è concisa come quella iniziale ma espande il codice ottenendo la seconda versione.

t9-2

Aggiustare il valore di ritorno

Manca ancora il risultato globale.
Come primo passo si può fare una piccola modifica a report-result così che ritorni il risultato del caso che sta testando

(defun report-result (result form)
  (format t "~:[FAIL~;pass~] ... ~a~%" result form)
  result)

Adesso sembrerebbe che basti un progn e un and per combinare i risultati. Purtroppo no, and non funziona per via del comportamento short-circuit, si fermerebbe al primo nil e salterebbe i test successivi. Common Lisp non fornisce quello che vogliamo ma ci sono le macros :wink:

Lasciamo da parte i test per vedere come fare la macro, chiamiamola combine-results:

(combine-results
  (foo)
  (bar)
  (baz))

avendo in mente qualcosa simile a:

(let ((result t))
  (unless (foo) (setf result nil))
  (unless (bar) (setf result nil))
  (unless (baz) (setf result nil))
  result)

L’unica cosa è che bisogna inserire la variabile result nell’espansione. Come visto precedentemente usare un nome letterale per una variabile può produrre leak nell’espansione della macro. Così occorre creare un nome unico, lavoro per with-gensym. Per comodità la riporto qui, metti che uno non l’ha presente :wink:

(defmacro with-gensyms ((&rest names) &body body)
  `(let ,(loop for n in names collect `(,n (gensym)))
     ,@body))

Si può così definire combine-results in questo modo:

(defmacro combine-results (&body forms)
  (with-gensyms (result)
    `(let ((,result t))
      ,@(loop for f in forms collect `(unless ,f (setf ,result nil)))
      ,result)))

e quindi aggiustare check semplicemente usando combine-results invece di progn:

(defmacro check (&body forms)
  `(combine-results
    ,@(loop for f in forms collect `(report-result ,f ',f))))

Con queste modifiche ottengo:

t9-3

E nel caso in cui uno (o più) test falliscano si ottiene nil:

t9-4

(continua, prossimamente) :mrgreen:

Lisp – Macros, definirne di nostre – 3

l2
Si continua, come al solito copiando da qui.
Post complicato oggi, di quelli che poi ti tornano di notte… Avvisati, nèh!

Macros che scrivono macros

Se possiamo scrivere macros che scrivono funzioni, inserendo astrazioni nei compiti comuni allora perché non approfittarne in pieno e applicare lo stesso procedimento anche per le macros?
Nota personale: ecco, quando 30+ anni fa ho letto GEB di DRH mi son detto “WOW, UAU” anche per questo; lavoravo in Fortran al tempo.

Seguendo il Peter (devo comprare il suo libro di carta, usarlo come testo sacro) scriverò una macro gensym-osa che scrive una macro:

[…] a macro that generates code that generates code. While complex macro-writing macros can be a bit confusing until you get used to keeping the various levels of code clear in your mind, with-gensyms is fairly straightforward and will serve as a useful but not too strenuous mental limbering exercise.

Ecco, proprio come da nota precedente. Sto ringiovanendo :grin:

Vogliamo scrivere qualcosa come questo:

(defmacro do-primes ((var start end) &body body)
  (with-gensyms (ending-value-name)
    `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
          (,ending-value-name ,end))
         ((> ,var ,ending-value-name))
       ,@body)))

e renderla equivalente alla precedente versione di do-primes. In altre parole con with-gensym dobbiamo espandere in un let ogni variabile, ending-value-name in questo caso in un simbolo gensym-ico. Questo è semplice con un semplice modello di backquote.

(defmacro with-gensyms ((&rest names) &body body)
  `(let ,(loop for n in names collect `(,n (gensym)))
     ,@body))

Notare l’uso della virgola per interpolare il valore nell’espressione di loop.
Il ciclo genera una lista di binding forms ognuna delle quali consiste in una lista contenente uno dei nomi dati da with-gensym e dal codice letterale (gensym).
Si può testare questo codice il loop genera sostituendo i nomi con una lista di simboli:

l8-4

Dopo la lista delle binding forms il corpo di with-gensym è inserito nel corpo di let. Allora nel codice che costituisce la with-gensym ci si può riferire alle variabili nominate nella lista di variabili passate da with-gensym.

Espandendo la form di with-gensym nella nuova definizione di do-primes risulta qualcosa del genere:

(let ((ending-value-name (gensym)))
  `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
        (,ending-value-name ,end))
       ((> ,var ,ending-value-name))
     ,@body))

Sembra buona (cit.). Anche se sembra triviale è importante aver chiaro come le differenti macros sono espanse: quando si compila defmacro per do-primes la form with-gensym è espansa nel codice appena visto e compilato. Quindi la versione compilata di do-primes è esattamente la stessa che avevamo scritto a mano con il let esterno. Quando si compila una funzione che usa do-primes il codice generato da with-gensym gira generando l’espansione di do-primes ma la with-gensym stessa non è necessaria per compilare la form di do-primes perché è già stata compilata quando la do-primes venne compilata.
Continuando la nota di prima: mai detto che DHR sia facile :roll:

Un’altra classica macro-writing macro: once-only

Un’altra macro-writing macro classica è once-only che è usata per generare codice che valuta certi argomenti di una macro una volta sola e in un ordine particolare. Usando once-only si può scrivere do-primes quasi semplicemente come la prima versione leaky, così:

(defmacro do-primes ((var start end) &body body)
  (once-only (start end)
    `(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
         ((> ,var ,end))
       ,@body)))

Tuttavia –panico, almost– l’implementazione di once-only è –come dire, panicosassay– perché consiste in livelli multipli di backquoting e unquoting. Dice il Peter: ” If you really want to sharpen your macro chops, you can try to figure out how it works“. Comunque eccola:

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
      `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
        ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
           ,@body)))))

Altra nota: once-only è famosa, provate a googlare e vedrete. per esempio: Understanding how to implement once-only lisp macro,
oppure “Any concise description would be far too vague to grasp, but…“, qui, dove conclude con “The description here is frustratingly non-descriptive, and I apologize for that. If you understand once-only and can give a better explanation, I would be very grateful—not to mention completely awed“.

Oltre alle macros semplici

Ci sarebbe parecchio altro da dire sulle macros. Quello visto è solo robetta semplice, esempi che permettono di risparmiare un po’ di codice. Prossimamente ci saranno esempi di cose che senza senza le macros sarebbero impossibili.
Ad esempio no, pausa :mrgreen:

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Unisciti agli altri 84 follower