Scrivere funzioni newLisp auto-commentate

11 07 2009

google_translate.gif Translate To English!

Introduzione

Una cosa che mi piacque molto quando studiai per la prima volta Rebol (un bellissimo linguaggio di programmazione funzionale, creato da Carl Sassenrath), è che si potevano inserire dei commenti all’interno delle funzioni stesse. Cercherò di essere un pò più chiaro. Normalmente, se scrivo un programma in Java (ma anche in C, Pascal, Basic, etc…) scrivo i commenti alle funzioni in un modo simile al seguente:

/* Questo è il commento alla mia  bellissima funzione */
function miaFunzione() {
   faccioQualcosa();
}

Il commento (quello in mezzo ai simboli /*  */ ), anche se posto sopra alla funzione stessa, è però staccato, indipendente da essa, sotto ogni punto di vista. Solo utilizzando programmi esterni (come javadoc) si può allora generare della documentazione che “colleghi” il commento alla funzione. Inoltre, se spostassi la funzione, o la mettessi in un altro programma, potrei anche dimenticare di copiare il suo commento. Nei linguaggi funzionali come newLisp e Rebol, possiamo però sfruttare la loro omoiconicità per inserire il commento all’interno della funzione stessa. Questo ci porterà notevoli vantaggi:

  1. Se copio / sposto la funzione, il commento verrà copiato con essa, sempre.
  2. Molti linguaggi di scripting (compreso newLisp) hanno una comoda console per impartire i comandi. Questo significa che, si possono realizzare dei comandi ad-hoc, per leggere tali commenti, direttamente dalla funzione stessa, e senza ricorrere a programmi esterni (simili a javadoc).
  3. Il commento (e la documentazione in genere), non essendo necessario estrarlo con tools esterni, sarà sempre aggiornato (non rischio di avere una funzione X ma con un commento estratto Y – più vecchio della funzione stessa, solo perchè non ho rilanciato il comando di aggiornamento della documentazione).
Passiamo alla pratica

Ora che ho chiarito (spero!) un pò meglio la mia idea, cerchiamo di capire come metterlo in pratica usando il nostro newLisp. Prima di tutto scriviamo una funzione molto semplice:

(define (somma-valori argValore1 argValore2)
   (println (+ argValore1 argValore2) )
)
>> (somma-valori 10 20)
30

Bene, ora che ho la funzione, inseriamo un breve commento per spiegare il suo funzionamento. Lo possiamo fare in diversi modi, in base al numero di informazioni (e al tipo) da mettere. Il sistema più semplice è inserire una stringa che contenga il commento (ma newLisp ci permetterà di fare ben altro!):

(define (somma-valori argValore1 argValore2)
   "Questa funzione addiziona due numeri interi."
   (println (+ argValore1 argValore2) )
)

La possibilità unica nel suo genere che hanno i linguaggi omoiconici è che il codice (il programma vero e proprio) e i dati sono in realtà la stessa cosa, quindi newLisp non si “scandalizza” se, nel bel mezzo di un programma, inseriamo una stringa, senza chiamare funzioni, senza assegnazioni! Ma ora arriva la parte più interessante: proprio perchè in newLisp i dati sono codice, ed il codice sono dati, possiamo manipolare la nostra funzione come una lista qualunque, quindi:

>> (nth 1 somma-valori)
"Questa funzione addiziona due numeri interi."

Meraviglioso! Abbiamo potuto estrarre il commento dalla funzione! Ora guardate qui… create nel vostro ambiente newLisp (dalla console) questa funzione:

(define (helpme argFunction)
   (nth 1 argFunction)
)

Ora provate a digitare:

>> (helpme somma-valori)
"Questa funzione addiziona due numeri interi."

Fantastico! Basta quindi stabilire uno standard ed avremo, gratis (  :-)  ) , un comodo sistema di help online! Ma allora, perchè fermarci qui? Proviamo in questo modo:

(define (somma-valori argValore1 argValore2)
   (
      (comment "Questa funzione addiziona due numeri interi.")
      (version 1.1)
      (last-update "2009-07-01")
   )
   (println (+ argValore1 argValore2) )
)

Il nostro semplice commento, scritto sottoforma di stringa, ora è diventato una lista, contenente dei “tag” (comment / version / last-update) rileggibili dalla nostra funzione di help:

(define (helpme argFunction)
   ( println "DESCRIPTION: " (lookup 'comment (nth 1 argFunction)) )
   ( println "VERSION    : " (lookup 'version (nth 1 argFunction)) )
   ( println "LAST-UPDATE: " (lookup 'last-update (nth 1 argFunction)) "\n" )
)

>> (helpme somma-valori)
DESCRIPTION: Questa funzione addiziona due numeri interi.
VERSION    : 1.1
LAST-UPDATE: 2009-07-01

I vantaggi di un metodo simile sono evidenti: se aggiungessimo anche una funzione per indicizzare le funzioni scritte (quindi potendo anche cercarle scrivendo solo una parte del loro nome), allora avremo un ambiente di sviluppo e test di prim’ordine!

Anche stavolta siamo quasi alla fine. Spero che l’articolo possa fornirvi spunti utili al vostro lavoro e divertimento!

A presto!





(01) Code Pattern: Introduzione

23 06 2008

google_translate.gif Translate To English!

Come già anticipato, iniziamo una serie di puntate per parlare dei code patterns (una breve introduzione la potete trovare qui: http://newlisp.wordpress.com/2008/05/21/aggiornato-il-codepatterns/).

Essi sono elementi molto importanti, ed hanno diverse funzionalità:

  1. Insegnano il modo migliore per svolgere determinati compiti.
  2. Sono una fonte preziosa di codice o medotologie di programmazione già pronte all’uso.

Il documento originale, che useremo come guida “spirituale”, si trova qui: http://www.newlisp.org/CodePatterns.html

In alcuni punti ho aggiunto anche altre informazioni, al fine di chiarire dei concetti che potrebbero risultare poco chiari ai meno esperti. Ho inoltre aggiunto anche altri esempi, includendo delle opzioni o features non esplicitamente indicate nel documento originale.

Pronti… allora si inizia!!!

Script files

La linea comando

Nei sistemi Un*x-like (come Linux per esempio), potete eseguire gli script in modo automatico mettendo, nella prima riga dello script il comando seguente:

#!/usr/bin/newlisp

Per eseguire l’interprete, indicando però di allocare uno stack più grande, si può dare il comando:

#!/usr/bin/newlisp -s 100000

Oppure…

#!/usr/bin/newlisp -s100000

In base alla shell che usate (cmd per Windows, oppure una delle innumerevoli shell disponibile in Un*x), newLisp accetta sia i comandi attaccati che staccati. Per verificare tutto ciò potete usare il seguente codice:

#!/usr/bin/newlisp -s 100000 -m 10
(println (main-args))
(println (sys-info))

Richiamandolo questo script, dovreste ottenere qualcosa simile a:

("/usr/bin/newlisp" "-s" "100000" "-m" "10" "./arg-test")
(308 655360 299 2 0 100000 8410 2)

NOTA: lo stack allocato per default (se non diversamente specificato) è pari a 2048, considerando il fatto che, ad ogni cella corrispondono circa 80 bytes.

Usare le pipes

Per poter inviare i risultati di un comando shell in uno script newLisp si può usare il codice seguente (salvatelo con il nome uppercase.lsp):

#!/usr/bin/newlisp

# uppercase - converte un input in maiuscolo.
# usage (un*x):
#          ./uppercase < file-spec
# usage (Ms Windows):
#          newlisp uppercase.lsp < my-text

(while (read-line) (println (upper-case (current-line))))
(exit)

Il breve script convertirà tutte le righe che giungono dalla console in MAIUSCOLO.

Oltre alla sintassi sopra indicata (vedi lo script), potete digitare anche questo comando:

type my-text | uppercase.lsp

Per oggi è tutto, la prossima volta parleremo di:

  • Moduli;
  • Contexts;
  • Rendere un programma modulare e riutilizzabile.

A presto!





Realizzare le closures (chiusure) in newLisp

29 05 2008

google_translate.gif Translate To English!

Qualche giorno fa` mi sono imbattuto in un argomento affascinante quanto “spinoso”: le closures. Cosa sono?
In breve (e per farla semplice), una chiusura e` una funzione che viene valutata in un ambiente da cui ha effettuato il bind delle variabili con l’ambiente da cui viene chiamata. Ok ok, ho capito che non si e` capito molto! Le definizioni non sempre spiegano qualcosa al meglio! Passiamo allora ad un esempio pratico.

Cerchero` di spiegare cosa sono le closures utilizzando un linguaggio “astratto”, giusto per apprenderne il concetto base, poi passeremo all’implementazione in newLisp (per chi conosce java, diciamo che una closure e` assimilabile ad una inner class, mentre per chi conosce javascript una closure e` una funzione in un’altra funzione).

Analizziamo il codice seguente (astratto, cioe` non scritto in nessun linguaggio di programmazione in particolare):

FUNCTION moltiplicaFattore(argFattore) {
  return(
    FUNCTION(argValore ) {
      return argValore * argFattore
    }
  )
}

Come si usa?

>> calcola_3 = moltiplicaFattore( 3 )
>> calcola_3( 5 )
15
>>

Ora forse iniziate ad intuire qualcosa… analizziamo il codice in dettaglio.
Lo scopo e` quello di realizzare un generatore di funzioni in grado di moltiplicare un valore ( calcola ) per un coefficiente dato ( moltiplicaFattore ).
Prima di tutto ho creato una funzione chiamata moltiplicaFattore() a cui passo un coefficiente, il quale rappresenta il fattore da moltiplicare. Quindi se gli passo “4″, potro` fare calcoli tipo: (3 * 4), oppure (12 * 4), oppure (897 * 4). Quando chiamo questa funzione, essa mi restituisce una nuova funzione, che accetta un solo altro parametro, il quale altro non e` che il valore che voglio moltiplicare per il coefficiente precedentemente dato.

La caratteristica peculiare di queste due funzioni e` che, in quella interna, sfrutto una variabile assegnata in un momento precedente. Questo si puo` fare perche` la funzione interna e` legata al contesto delle variabili definite per moltiplicaFattore(), quindi tale funzione puo` usare le variabili definite in moltiplicaFattore(), anche se essa viene poi richiamata in modo indipendente (vedi esempio: calcola_3( 5 ) ).

In javascript si realizza una cosa simile con il codice seguente (javascript ha le closures):

function moltiplicaFattore(argFattore) {
  return( function(argValore) { return(argFattore * argValore ) ; } );
}

newLisp purtroppo non supporta le closures, in quanto il linguaggio e` dynamically scoped, quindi ad ogni chiamata di funzione i simboli che vanno in conflitto con quelli definiti nello spazio dei nomi della funzione chiamante vengono automaticamente “accantonati” nello stack. Quindi:

(define (contenitore arg1)
    (fn (arg2)
      (println "Arg1: " arg1 " --> Arg2: " arg2 )
    )
  )

Dara` come risultato:

>> (setq mioContenitore (contenitore "pippo" ) )
>> (mioContenitore "B" )
Arg1: nil --> Arg2: B

Poiche`, come recita il manuale, ogni simbolo non espressamente definito viene automaticamente definito ed inizializzato a nil, allora il simbolo arg1 chiamato nella funzione lambda viene inizializzato a nil (vedi output), mentre arg2 viene regolarmente assegnato.

Ora che abbiamo capito (!) cosa sono le closures, e abbiamo anche capito che newLisp non le supporta, come possiamo fare per implementarle ugualmente senza fare le cose troppo “sporche”?

Furtunatamente newLisp ci fornisce un altro strumento, altrettanto potente ed utile: i context.

Un context e` un modo che si usa per separare fisicamente i simboli uguali ma con significati diversi, dando loro la possibilita` di “vivere” in aree diverse (lo spazio dei nomi). E` grazie ai context che possiamo implementare funzionalita` tipiche dei lexically scoped enviroments, le cui funzioni si “comportano” in modo diverso in base al contesto in cui si trovano. Forse, con un azzardo, facendo un paragone con un linguaggio ad oggetti imperativo, potremmo dire che il nostro context sia una classe di un oggetto, mentre le funzioni sono i metodi dell’oggetto stesso.
Passiamo subito alla pratica! Scriviamo allora un programmino in newLisp che realizzi (simuli…) una closure. Dopo aver letto un po` di materiale e soluzioni possibili, sono giunto al seguente codice:

(define (MoltiplicaFattore:MoltiplicaFattore argFattore )
	(if (number? MoltiplicaFattore:fattore )             ; IF
		(* argFattore MoltiplicaFattore:fattore )    ; THEN
		(setq MoltiplicaFattore:fattore argFattore ) ; ELSE
        );if
)

La funzione e` molto compatta, e data la sua struttura e` facilmente riadattabile in altri ambiti. Si usa cosi:

>> ((new 'MoltiplicaFattore 'calcola_3 ) 3 )
>> (calcola_3 8 )
24

Il “trucco” e` nello sfruttare alcune caratteristiche dei context:

  1. Un context puo` includere un functor, cioe` una funzione che viene richiamata per default se non ne viene chiamata nessuna in modo esplicito.
  2. Da un context posso derivarne altri, tramite la funzione new.

Se osservate il programma, vi accorgerete che ho definito un nuovo context chiamato MoltiplicaFattore, al cui interno definisco una variabile chiamata fattore. La prima volta che create il context (e viene chiamato per default il functor MoltiplicaFattore:MoltiplicaFattore) mi “accorgo” che la variabile fattore non e` stata ancora definita (tramite la funzione number? ), e quindi creo la variabile fattore impostandola a argFattore. Le volte successive che richiamo il context (quello nuovo appena istanziato, cioe` calcola_3) la variabile interna al context fattore ora e` definita, e quindi la uso come parametro! ;-)

Questo metodo ci permette anche di cambiare la radice di moltiplicazione:

(setq MoltiplicaFattore:fattore 5 )

Riprendendo il precedente esempio (che riscrivo completamente, per comodita` di lettura)…

>> ((new 'MoltiplicaFattore 'calcola_3 ) 3 )
>> (calcola_3 8 )
24
>> (setq calcola_3:fattore 5 )
>> (calcola_3 8 )
40

Vittoria! Ora abbiamo anche noi le nostre closures! :-)

Bene, ora ci meritiamo proprio una pausa… fino al prossimo articolo!

A presto!!!