Mappare le colonne con i loro nomi

30 01 2008

google_translate.gif Translate To English!

Translate to English Usando i drivers ODBC con newLisp vi accorgerete ben presto che, a seguito di una SELECT, vi verranno ritornati i dati, riga per riga (usando ODBC:fetch-row), in una lista. Il problema e` che sarete costretti a prendere i dati di ogni campo usando la posizione (“0″ per il primo campo; “1″ per il secondo; etc…). Sarebbe molto piu` pratico trovare i campi per nome.

NewLisp non finira` mai di stupirmi: ho iniziato ad affrontare il problema questa mattina, e l’ho risolto in pochissimo tempo. Ma cio` che mi ha colpito di piu` e` l’eleganza con cui si possono fare queste cose.
Questo e` il codice per fare la mappatura:

(let (conta -1) (setq mappatura (map (fn(x) (inc 'conta) (list conta (ODBC:column-atts x) ) ) (sequence 1 (ODBC:num-cols)))) )

Ok, ok… avete ragione… scriviamolo un po` meglio! :-)

(let (conta -1)
  (setq mappatura(map
      (fn(x)
        (inc 'conta)
        (list conta (ODBC:column-atts x) )

      );fn

      (sequence 1 (ODBC:num-cols) ) )
    );map
  );setq
);let

Pulito ed elegante:espressivo. Sono sicuro che si potrebbero fare molte ottimizzazioni, ma questo l’ho fatto in pochissimo, e funziona benissimo!

Come si usa? Cosa fa`?

L’uso e` semplice: aprite una connessione al vostro DB (ODBC:connect), poi lanciate una query SELECT (ODBC:query). Poi chiamiamo una fetch… e` tutto (ODBC:fetch-row).

Facendo girare il precedente programmino, esso vi restituira` una lista con il numero del campo ed il suo nome. Esempio:

(0 ("STATUS00                         " 12 30))
(1 ("ACTION00                         " 12 5))
(2 ("MACHINE00                        " 12 30))
(3 ("COD_PRJ00                        " 12 13))

A questo punto la variabile mappatura conterra` i dati precedenti. Quindi, usando il lookup, completeremo la magia! Quindi…

> (lookup 4 mappatura)
("MACHINE00                        " 12 30)

Quindi…

> (string ((lookup 4 mappatura) 0))
"MACHINE"

Semplicissimo!

Ma cosa fa` il programma esattamente? Analizziamolo pezzo per pezzo.

L’espressione (let conta -1 mi permette di creare una variabile locale, con valore -1.
L’espressione (sequence 1 (ODBC:num-cols) ) genera una lista di numeri da 1 al numero di colonne ottenute come risultato della SELECT (nel nostro caso sono 4).
La funzione lamba fn(x) (vi ricordo che la dicitura fn e` sinonimo di lambda), grazie all’uso della funzione map, prende i valori della sequence precedente, un valore alla volta, (nel nostro caso 1, 2, 3, 4), e restituisce una nuova lista composta dal contatore (conta) e dai dati restituiti dalla funzione ODBC:column-atts.
Quest’ultima funzione restituisce il nome della colonna, il tipo di dato (stringa/intero/etc…), e la lunghezza di una colonna nella posizione x; scrivendo quindi (ODBC:column-atts 3) otterremo i dati della prima colonna (si inizia a contare da uno):

("MACHINE00                        " 12 30)

La variabile conta, essendo esterna alla funzione, viene incrementata ogni volta, ed indica la posizione del campo nella lista del record prelevato.

La funzione map e` molto potente, espressiva ed elegante. Grazie al suo utilizzo, unito ad una funzione lambda, siamo riusciti a fare quello che, in moltissimi altri linguaggi, ci avrebbe costretto a cicli FOR, creazione di variabili temporanee, etc…

NewLisp e` magnifico!

A presto!





Query parametriche

30 01 2008

google_translate.gif Translate To English!

Poco fa` mi sono trovato di fronte ad un problema piuttosto interessante. Stavo scrivendo una query per un database Oracle, e dovevo rendere questa query parametrizzabile.
La query era simile alla seguente:

SELECT * FROM MiaTable WHERE campo1 = :parametro1 AND campo2 = :parametro2

Il campo1 era un numero intero, mentre il campo2 era una stringa.

Ho allora creato una piccola funzione in grado di sostituire automaticamente, e in un colpo solo, tutti i parametri della query. Inoltre la stessa funzione, in base alle indicazioni fornite, poteva formattare i parametri nel modo corretto (per esempio aggiungendo gli apici singoli intorno alla stringa).

La funzione e` questa:

(define (replace-sql argSql argReplace , (delimiter "") )
	(dolist (i argReplace)
 		(if (= (i 1) 'string) (setq delimiter "'"))
		(replace (i 0) argSql (string delimiter (i 2) delimiter)  )
	 );dolist

	argSql	; Valore di ritorno
)

Come si usa? Semplicissimo!
Il primo parametro e` la query stessa (non importa se e` una SELECT, una INSERT o altro!) ed il secondo parametro e` una lista di elementi da sostituire nella query. Ecco un esempio completo:

;Query parametrica.
(setq sql "select * from myTable where t1 = :t1 and t2 = :t2")

; Questi sono i parametri da sostituire.
(setq valori '(
    (":t1" integer "100")
    (":t2" string "Pippo")
) )

(replace-sql sql valori)

La variabile valori contiene delle liste, in cui ognuna e` cosi` composta:

  1. Nome completo del parametro (esempio: “:t1″)
  2. Tipo di dato (per ora solo “string” esegue una vera formattazione aggiungendo gli apici; potete pero` modificare molto facilmente la funzione originale aggiungendo altri tipi di dato).
  3. Valore del campo (esempio: “Pippo”)

La funzione ritornerà il codice SQL con tutti i valori correttamente sostituiti.

Attenzione: poiche` viene eseguito un “brutale” replace, non usate parametri che possano avere nomi che compaiono in altre parti del codice sql stesso, altrimenti effettuerete il replace anche di altre parti di codice! Vi suggerisco nomi tipo: <par1> oppure @par1@.

Spero che questa funzione vi possa essere utile!

Alla prossima!

UPDATE: su suggerimento di Carlo (grazie!), ho modificato la funzione per poter riconoscere, in modo automatico, il datatype dei parametri.
La funzione completa e` la seguente:
;==============================================================================
; @SYNTAX (replace-params SQL-Code parameters-list)
;	SQL-Code	: SQL statement.
;	parameters-list	: List composed in this way:
;			     ( params01 params02 params03 ... )
;			  Every "param" is a list, and is composed in this way:
;			     (param-name datatype value)
;				param-name	: name of the parameter
;				value		: value that will substitute the parameter (string)
;				datatype	: datatype (e.g.: integer, string, date, etc...).
;						  This value is optional. If not passed, it will be automatically
;						  detected from the function self.
;
;	Currently, parameter type can be one of the following:
;		string	: the value will be surrounded with single apex.
;		date	: the value must be in the following format: DD-MM-YYYY.
;					It will be automatically formatted by this function.
;		float	:
;		integer	: will be inserted without any other extra-job.
;
; @RETURN SQL string with all requested terms replaced.
;
; Replace one or more sql parameters with the real values.
;
; @EXAMPLE
;	In this example, the original sql string is "sql". The variable "val"
;	contains two lists that contain needed info to make the replacement.
;		":t1" is INTEGER and the value is "100"
;		":t2" is STRING and the value is "Alessandro"
;
; 		(setq sql "select * from myTable where t1 = :t1 and t2 = :t2")
;
; 		(setq val '(
; 			(":t1" integer "100")
; 			(":t2" "Alessandro")
; 		) )
;
;		(replace-params sql val)
;==============================================================================

(define (replace-params argSql argReplace ,
	(locPrefix "") 		; string before the value
	(locPostfix "")		; string after the value
	(locDatatype nil)	; datatype for a specific value
)
	(dolist (i argReplace)
		(if (= (length i) 2)	; Datatype is missing: I will take the default one.
			(cond 						;IF
				( (string? (i 1) ) 	(setq locDatatype 'string) )
				( (integer? (i 1)) 	(setq locDatatype 'integer) )
				( (float? (i 1) ) 	(setq locDatatype 'float) )
			);cond
			(setq locDatatype (i 2)	)	;ELSE
		);if

		(cond
			(;condition_1
				(= locDatatype 'string) (setq locPrefix "'" locPostfix "'")
			)
			(;condition_2
				(= locDatatype 'date) (setq locPrefix "to_date(" locPostfix {, 'DD-MM-YYYY')} )
			)
		);cond

		(replace (i 0) argSql (string locPrefix (i 1) locPostfix)  )
	);dolist

	argSql
)




Breve introduzione a NewLisp.

29 01 2008

Introduzione

NewLisp e` un linguaggio di programmazione funzionale… ma cosa e` esattamente un linguaggio funzionale? Partiamo dalla definizione data da Wikipedia:

Programmazione funzionale è un paradigma di programmazione in cui il flusso di esecuzione del programma assume la forma di una serie di valutazioni di funzioni matematiche. Solitamente questo approccio viene usato maggiormente in ambiti accademici piuttosto che industriali. Il punto di forza principale di questo paradigma è la mancanza di effetti collaterali (side-effect) delle funzioni, il che comporta una più facile verifica della correttezza e della mancanza di bug del programma e la possibilità di una maggiore ottimizzazione dello stesso. Un uso particolare del paradigma, per l’ottimizzazione dei programmi, è quello di trasformare gli stessi per utilizzarli nella programmazione parallela.

Prendiamo anche quest’altra definizione:

Si dice che una funzione produce un effetto collaterale quando modifica un valore o uno stato al di fuori del proprio scoping locale. Per esempio, una funzione ha un effetto collaterale quando modifica una variabile globale o statica, quando modifica uno dei suoi argomenti[1], quando scrive dati su di un display o su di un file o quando invoca altre funzioni con effetti collaterali.
Gli effetti collaterali presenti in un programma spesso rendono difficile la comprensione del suo flusso di esecuzione e se non gestiti bene possono portare facilmente a bug difficili da scovare.

NewLisp non e` un linguaggio funzionale puro, nel senso che permette di fare delle cose che in un linguaggio funzionale “puro” non si dovrebbero poter fare. Anche se questo puo` far storcere il naso a qualcuno, io credo che sia in realta` un punto vincente di NewLisp, in quanto alcuni vincoli imposti dai linguaggi funzionali puri potrebbero non adattarsi al meglio ad un linguaggio di scripting come questo.

(un’altra interessante definizione sui linguaggi funzionali la potete trovare qui: http://programmazione.it/index.php?entity=eitem&idItem=30406)

Installare NewLisp

NewLisp puo` essere scaricato dal sito seguente (area download):

http://newlisp.org/index.cgi?page=Downloads

Ci sono le versioni per Windows, per Linux, per Mac OSX. Inoltre, trovate la release stabile e quella attualmente in sviluppo (che, comunque, e` sufficientemente stabile per poterci lavorare).
Si hanno a disposizione tre ambienti di sviluppo, uno in console, uno in Tk, e l’altro in Java.

Diamo una prima occhiata al linguaggio!

Alcune caratteristiche di NewLisp hanno sono di forte impatto per chi viene dai linguaggi imperativi (come il C, PHP, Pascal, Java, C#, Visual Basic, etc…). Uno di questi e` che, in NewLisp, ogni cosa e` un simbolo. Cosa significa? Nei linguaggi imperativi esiste una netta differenza tra dati e programma, mentre NewLisp tratta tutte le informazioni genericamente come “simboli”.
Analizziamo l’espressione seguente:

> (+ 7 5)
12

E` una espressione scritta in NewLisp. Il Lisp (e suoi derivati) e` “rinomato” per l’uso smodato delle parentesi. Vedremo pero` in seguito che, questo apparente problema, e` in realta` una feature notevole. Nel nostro caso la funzione “+” permette di sommare un numero indeterminato di elementi, compresi nelle parentesi. Possiamo quindi scrivere:

 > (+ 1 2 3 4 5 6 7 8 9 10)
55

Qui gia` abbiamo un “assaggio” del vantaggio dell’uso delle parentesi. Il loro utilizzo ci permette di delimitare il range di azione di una funzione. In un linguaggio imperativo, la precedente funzione potrebbe essere scritta come:

]>> 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10

Oppure:

]>> sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Il “+” e` un simbolo, ed in base al suo uso noi chiederemo a NewLisp di valutare l’espressione “+” o no. Potremmo infatti scrivere:

> (set 'res (+ 7 5) )
12
> (set 'res '(+ 7 5) )
(+ 7 5)

Notate la differenza: nel primo caso abbiamo chiesto di calcolare la somma dei valori 7 e 5 e di assegnare il risultato (con set) alla variabile res. NewLisp ci mostra “12″. Nel secondo caso abbiamo chiesto a NewLisp di assegnare alla variabile res la lista (+ 7 5) (notate il piccolo apice ” ” davanti alla parentesi aperta della lista: ” (+ 7 5) “). NewLisp non si scompone: o valuta un simbolo e le espressioni (e ci fornisce come risultato 12) oppure non lo valuta.
Le parentesi denotano un elemento fondamentale in NewLisp: le liste. Tutti i simboli sono contenuti in liste (non a caso il termine Lisp proviene da List Processor), e noi dobbiamo solamente dire a NewLisp quando valutare una lista (set ‘res (+ 7 5) ) e quando non farlo (set ‘res ‘(+ 7 5) ).

Il tutto e` molto semplice.

Prima di concludere questa breve introduzione, vorrei sottolineare un altro elemento fondamentale: NewLisp, essendo un linguaggio funzionale, usa esclusivamente espressioni, in ogni luogo, al contrario dei linguaggi imperativi che possono usare le espressioni solo nella parte destra di una assegnazione.
Per chiarire il concetto, vediamo l’esempio seguente:

> (set 'res (+ 7 5) )
12
]>> res = 7 + 5
12

In NewLisp abbiamo usato due espressioni annidate: “(set” e “(+”. Non esiste il concetto di left-side e right-side. Nel secondo esempio (scritto in un generico linguaggio imperativo), la parte sinistra denota la variabile, mentre la parte destra include una espressione.
Questa differenza, apparentemente irrilevante, permette invece di creare programmi molto espressivi e potenti.
Prendiamo, per esempio, la funzione map di NewLisp:

> (map println '("Hello" "World"))
Hello
World

Quello che abbiamo fatto e` mappare una funzione su una lista! La funzione println (stampa qualcosa) e` stata “mappata” sulla lista seguente (la quale non e` stata valutata perche` preceduta dall’apice singolo ” “). L’esempio e` gia` notevole, ma proviamo ad estenderlo un po`:

(set 'counter 1)

(map
    (fn (i)
        (println "Elemento " counter ": " i)
        (inc 'counter)
    );fn

    '(-3 -2 -1 0 1 2 3)
);map

Eseguendolo, otteniamo:

Elemento 1: -3
Elemento 2: -2
Elemento 3: -1
Elemento 4: 0
Elemento 5: 1
Elemento 6: 2
Elemento 7: 3

Il programma e` molto semplice (ho dato una sistemata alle parentesi per renderlo comprensibile al massimo, dandogli una forma tipicamente usata nei linguaggi imperativi).

Nella prima riga assegnamo il valore 1 alla variabile counter.
Poi usiamo la funzione map applicando una funzione-lambda (vedremo nel prossimo tutorial di cosa si tratta; in breve diciamo che e` una funzione senza un nome…) alla sequenza di numeri (-3 -2 -1 0 1 2 3).

Mappare una funzione con un’altra funzione e` solo la punta dell’iceberg (soprattutto se la funzione mappata non ha nome!): ci sono altri sistemi di mappatura creati ad-hoc; voi stessi potrete realizzare funzioni native (al pari di una funzione NewLisp!) che implementano questo metodo.

Tutto cio` e` possibile grazie a cio` che abbiamo detto poco fa` riguardo le espressioni e il left-side e right-side.

Nella prossima puntata inizieremo ad approfondire questi concetti, scoprendo le potenzialita` di questo bellissimo linguaggio.

A presto!