Lisp – funzioni – 3

gs4Sì, si continua, grazie Peter 😀

Valori ritornati dalla funzione

Finora abbiamo sempre usato il comportamento abituale di ritornare l’ultima espressione valutata dalla funzione.

A volte però è conveniente ritornare nel mezzo della funzione, per esempio quando ci sono costrutti di controllo annidati. Nota personale: era una pratica giudicata cattiva dei vecchi programmatori Fortran; con il Lisp mi trovo cose venute molto dopo (p.es. sugli argomenti e come passarli) e cose molto vecchie, come questa. In questi casi si usa l’operatore speciale return-from per ritornare immediatamente un valore dalla funzione.

Vedremo più avanti che in realtà return-from non è legato alla funzione ma a un blocco di codice definito con l’operatore speciale block. Ma è OK anche con defun (questo mi ricorda l’Algol). return-from è un operatore speciale il cui primo argomento è il nome del blocco da cui si ritorna. Il nome non è valutato quindi non è quotato. C’è anche la macro return ma non vale per le funzioni, vedremo poi…

L’esempio seguente usa cicli annidati ed esce quando si supera un certo valore (uno skifo, ma è un esempio):

f0

Uh! Peter mi da ragione: return-from è poco usata.

Funzioni come dati, a.k.a. funzioni Higher-Order

Mentre il modo principale di usare le funzioni è quello di chiamarle per nome ci sono situazioni in cui è utile trattare la funzione come dati. per esempio se si può passare una funzione come argomento a un’altra si può scrivere una funzione di ordinamento (sorting) lasciando al chiamante di definire la funzione di comparazione degli elementi. Questo algoritmo può quindi essere usato per diverse funzioni di comparazione. Similmente callbacks e hooks dipendono dalla capacità di memorizzare riferimenti a codice da eseguire più tardi. Essendo le funzioni il modo standard di astrarre il codice avere funzioni trattabili come dati ha senso. Cosa che fanno altri linguaggi e allora anche noi 😉

Nel Lisp le funzioni sono solo un tipo di oggetti. Quando se ne definisce una con defun si fanno due cose: si crea una nuova funzione e le si da un nome. Ma è possibile usare espressioni lambda per creare funzioni senza nome. la distinzione tra funzione con nome e anonima è opaca per cui tutto quello che serve è solo di saper come trattenere questi dati e come invocarli.

L’operatore speciale function fornisce un meccanismo per avere l’oggetto funzione. Prende un singolo argomento e ritorna la funzione con questo nome. Esempio: definiamo la funzione foo così:

f1

è possibile avere l’oggetto funzione, così:

f2

Ma si può anche scrivere:

f3

Una volta che si ha l’oggetto funzione l’unica cosa che si può farne è invocarlo. Common Lisp ha due funzioni per invocare una funzione: funcall e apply. (OK, ce ne sarebbe anche un terzo: multiple-value-call, vedremo dopo). Differiscono solo per come ottengono gli argomenti da passare alla funzione.

Quando si conosce il numero di argomenti da passare alla funzione  nel momento in cui si scrive il codice si usa funcall. Il primo argomento di funcall è l’oggetto funzione da invocare e il resto sono gli argomenti da passare a questa funzione. Per cui (foo 1 2 3) è equivalente a (funcall #'foo 1 2 3)).
Tuttavia… serve? probabilmente le due espressioni precedenti vengono copilate allo stesso modo.
Un esempio d’uso più razionale di funcall: disegnare un istogramma ASCII dei valori ritornati da una funzione argomento quando invocata con valori da minimo a massimo, step by step:

f4

L’espressione funcall calcola il valore della funzione per ogni valore di i; il loop interno usa questo valore calcolato per determinare quanti asterischi scrivere sullo standard output.
Notare che non si usa function o #' per avere il valore di fn; vogliamo che sia interpretato come una variabile perché è il valore di questa variabile che dev’essere l’oggetto funzione. Si può chiamare plot con qualunque funzione che abbia un singolo argomento, per esempio la built-in exp (e elevato a x).

f5

Ma funcall non serve quando la lista degli argomenti è nota solo a runtime. Per esempio, rimanendo sulla funzione plot supponiamo di avere un lista contenente un oggetto funzione, un valore minimo, uno massimo e un passo. Cioè gli argomenti che voglio passare a plot. supponiamo che questa lista sia nella variabile plot-data. Si può invocare plot con i valori della lista, così:

(plot (first plot-data) (second plot-data) (third plot-data) (fourth plot-data))

funziona, ma non è elegante, anzi noioso quanto mai.
E qui è dove entra in gioco apply. Come per funcall il primo argomento è l’oggetto funzione. Ma dopo invece di un singolo argomento si aspetta una lista. E applica la funzione ai valori nella lista, cosa che consente di scrivere (apply #'plot plot-data).

Inoltre apply accetta più argomenti basta che l’ultimo sia una lista. Per cui se plot-data contiene solo i valori min, max e step puoi usare apply per plottare exp in questo modo:

f6

apply consente di usare argomenti &optional, &rest o &key, basta che l’ultimo sia una lista, con elementi del tipo che la funzione si aspetta. (Panico? un pochino, solo un pochino :oops:).

Funzioni anonime

Capita che sia noioso dover dare un nome a ogni funzione, specie se non la si chiamerà mai per nome. Si può allora creare una funzione anonima con un espressione lambda con questa forma:

(lambda (parameters) body)

Si può pensare alla lambda expression come a una funzione speciale dove il nome descrive quello che la funzione fa. Questo spiega perché si può usare una lambda expression al posto del nome della funzione con #':

f7

Si può anche usare una lambda expression come nome di funzione in una chiamata a funzione. Per esempio la funcall precedente può essere semplificata così:

f8

OK, non serve a niente ma è per far vedere che la lambda expression può essere usata ovunque un normale nome di funzione può. Per ragioni storiche (sto semplificando tanto c’è il testo del Peter) per lambda posso anche non mettere #'.

Le funzioni anonime possono essere utili quando devi passare una funzione come argomento a un’altra funzione e la funzione da passare è semplice da poter essere espressa inline. Per esempio plottiamo la unzione 2x. Si potrebbe fare:

f9

e funziona ma più semplicemente:

f10

lambda ha un altro uso importante: creare closures, funzioni che catturano parte dell’ambiente dove sono create. Già viste brevemente nell’esercizio del DB ma prossimamente… :mrgreen:

Posta un commento o usa questo indirizzo per il trackback.

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: