
Porto un temps mirant-me s-langs com lisp, scheme... primer per curiositat, després per interés, després per aplicacions pràctiques (wistumbler2 scripting) i finalment per pures finalitats pajilleres i morbosses.
Malgrat sigui un llenguatge dels anys 70, ofereix una potencia i simplicitat que pocs altres llenguatges han aconseguit. Desgraciadament avui en dia els llenguatges
funcionals no sòn gaire utilitzats ja que han anat
triumfant llenguatges imperatius (aka C, Java, ...)
Els motius? Els llenguatges procedurals i imperatius són força més senzills de programar per força bruta o extreme programming en canvi els funcionals acostumen a tenir una base matemàtica que fa agafar por a molta gent, pero que ens donen una riquesa molt morbosa en quant a sintaxi i ens permet simplificar moltes tasques de forma senzilla. També cal dir que sense recursivitat es complica bastant més un codi funcional que un procedural, pero sempre ens quedaran la
tail-recursivity.
Que carat es un s-lang?
Els s-langs són llenguatges construits a partir de s-expressions. Fins aqui us quedeu exactament igual oi? x"D ..Resumint les s-expressions són aquelles formades per parentesis a tort i dret.
Al principi resulta complexa la lectura d's-langs, pero poc a poc veureu que es força interessant. Amb s-expressions podem construir qualsevol tipus de estructura de dades o codi (arrays, condicionals, bucles, etc).
Hi han dos elements basics en una maquina LISP. Els
atoms i els
cons. Els atoms son basicament unitats indivisibles que ocupen una cel.la de memoria i els cons són una array de dos posicions amb 1 atom a cada cel.la.
A partir d'aquestes dues unitats minimes podem construir-ho tot: llistes enllaçades, arrays, estructures, condicionals, etc.
console:
$ csi -q
#;1> (cons 1 2)
(1 . 2)
#;2> '(1 . 2)
(1 . 2)
#;3> (quote (1 . 2))
(1 . 2)
Si us fixeu, aqui he hagut de
quotejar la segona expressió. Els S-Langs el que fan es buscar en la taula de simbols el primer element d'un cons i executar aquell procediment amb una llista de parametres que li passem a continuació. Per tant si volem estalviar-nos utilitzar el procediment "cons" podem fer-ho directament amb un quote, que també pot ser escrit de la tercera forma.
Perquè scheme?
Hi han milers de s-langs i milers d'implementacions lliures per cada s-lang, tenia clar que no volia utilitzar elisp (aka emacs lisp), i el clisp es un monstru exagerat ja que l'estandar ha anat creixent de forma desmesuarada.. aixi que vaig anar a parar a l'Scheme.
Scheme es un dialecte de LISP. LISP va neixer l'any 58 (després de fortran) i vol dir "LISt Processing". Scheme es un versió netejada de lisp per tal de simplificar la maquina virtual (o no tant virtual, pq a l'epoca hi havia maquines que executaven LISP nativament) i es va crear la convenció RnRS que preten estandaritzar tota la pesca. La 'n' de RnRS simbolitza l'any. Aquest any sortirà l'R6RS que definirà una forma estandar per definir moduls d'extensió. Ja que fins ara cada implementació de Scheme usa la seva propia. Originalment el nom d'scheme era 'schemer', pero degut a que programaven en una maquina amb S.O. ITS i que restringia els noms de fitxers a 6 caracters O:)
Finalment comentar que a
Lain (una serie anime que si no heu vist...heu de veure) parlen dels "Knights of the Eastern Calculus" que en la serie son un grup de hackers que han trobat un fallo en IPv6 el qual els permet redireccionar qualsevol conexió i interceptar-la.. Doncs bé el nom aquest surt d'una conya a partir d'un grup d'usuaris de Scheme del MIT que es feien dir (també en plan de conya "Knights of the Lambda Calculus". En referencia a lambda, el procediment principal per crear procediments anonims.
Serafins
Els serafins es la forma carinyosa que tinc per parlar dels SRFIs. Són extensions estandars del llenguatge. Que ens permeten treballar de forma comode amb llistes, strings, etc. Actualment n'hi han 78.
>>
SRFI dot schemers
>>
Final SRFIs
Implementacions
Com ja he comentat abans hi han milers d'implementacions lliures de Scheme. En Java, C, C++, parrot, etc.. Aixi que tenim per escollir

.. Algunes implementacions d'scheme son vils maquines virtuals i d'altres ens permeten compilar a natiu, amb un rendiment molt bó, ja que les s-expressions son molt facils de processar i scheme es força més facil d'optimitzar que lisp.
Pel wistumbler2 vaig utilitzar GUILE, que es la implementació de GNU de Scheme pensada per ser utilitzada com a llenguatge d'extensió per programes (El GIMP per exemple). Pero IMHO li falta bastant per tenir un bon suport de threads i extensions que necessitarem si volem programar a pelo amb scheme.
Remenant porai vaig anar a parar a
Chicken. Una implementació sota llicencia BSD de scheme que ens ofereix una pila d'extensions, suport d'interpret i compilador de Scheme a natiu (scheme -> c -> gcc -> elf).
Amb chicken podem fer gairebé de tot, i la interficie per saltar a codi natiu es realment senzillisima, després fare un hello world amb un modul natiu de chicken.
Si mireu la pagina de extensions de chicken veureu que hi han desde parsers XML, bindings a les X, opengl, cairo, smtp, pop3, , irc, mysql, postgresql, sha1, md5, etc..
Els ous (EGGs o extensions del chicken), s'instalen amb
"chicken-setup <nom-extensio>". Ell solet s'encarrega de descarregar-se els sources compilarlos i posar-los on toca
>>
Eggs - extensions pel chicken
Per Guile tenim extensions com guile-gnome que ens permet fer aplicacions de Gnome amb scheme. Hi ha una versió del guile-gnome compilada pel nokia770, perque poguem programar en scheme+gtk desde la pda <;D>. Ara si...un hello world de gtk+guile en el n770 li costa uns 12 segons en arrencar

. Chicken es mes rapid que guile, pero no esta portat encara.
Construint programes..
Que seria d'un tutorial sense un exemple de hello world? Doncs aqui el teniu
console:
(display "Hello Worm!")
Scheme al igual que altres s-langs ens permet redefinir qualsevol procediment (aka symbol) amb molt poques restriccions de caracters. Per exemple, per realitzar una operació matemàtica haurem d'utilitzar la notació polaca:
(+ 1 2). On el simbol '+' es un procediment natiu de scheme que procesa una suma entre els dos arguments. Tanmateix podem redefinir el procediment '+'
Els comentaris els podem fer de dues formes, al igual que amb molts altres llenguatges tenim els comentaris d'una linea que es fan exactament igual que en ensamblador: amb un punt i coma. I els comentaris de bloc que es fan amb '#|' i acabant amb '|#'.
Els simbols es defineixen de forma global o de forma local amb una sentencia '(begin (...))'.
Una cosa interessant es que per evitar problemes de variables no inicialitzades, tenim dues sentencies per tal de definir variables: (define) i (set!). La primera ens permet definir una variable i la segona redefinir el valor de la variable. Cal s'ha de dir que LISP ens permet tenir el mateix nom per una variable que per un procediment, pero aixo no es possible amb Scheme, ja que aixi s'eviten confussions
Els valors booleans true i false s'escriuen #t i #f respectivament.
Condicionals
Les condicionals es construeixen d'una forma molt natural seguint la sintaxi de les s-expressions:
console:
(if (= a 2)
(display "val 2")
(display "no val 2"))
Com podem veure la forma es: (if (..condicio..)(..then..)(..else..)). Normalment voldrem posar mès d'una sentencia dins del bloc "then" o "else", aixi que podem utilitzar (begin) d'aquesta forma:
console:
(if (= a 2)
(begin
(display "val 2")
(newline))
(display "no val 2"))
Si volem definir variables locals dins d'una s-expr podem fer-ho amb (let) passant-li com a argument una llista de cons amb variable-valor aixi:
console:
(let ((a 3)(b 4))
(display a)
(display b))
Scheme ens ofereix bastantes més condicionals com (when) i (unless), que son com (if) pero amb només el cos de (..then..) o de (..else..).
A l'hora de fer les condicionals podem utilitzar sentencies com (= a b), (> a b), (>= a b) per valors numerics i (equal?) per strings. Scheme també ens permet comprobar si una variable es de cert tipus amb (string?), (boolean?), (number?), etc..
Per exemple:
console:
(display "Type your password: ")
(define a (read-line))
(display "Again: ")
(define b (read-line))
(when (string=? a b)(display "Looks like you typed two different passwords"))
Lambda
Lambda s'utilitza per crear funcions anonimes, es a dir, procediments sense nom, generalment s'utilitza per passar funcions com a parametre, o per fer recursivitat o embedir un cert calcul dins d'una s-expressió.
Per definir un procediment ho farem aixi:
console:
(define (fun-name arg1 arg2)
(begin
(display (string-append "Arg1 = " arg1))
(newline)
(display (string-append "Arg2 = " arg2))
(newline)))
(fun-name 1 2)
Ara anem a definir un procediment amb lambda:
console:
(define add4
(let ((increment 4))
(lambda(y)(+ y increment))
(display (add4 3))
; escriurà 7
Llistes
Scheme al igual que lisp tenen un suport natiu per treballar amb llistes enllaçades o arrays dinamiques, cosa que ens permet un control força bó per manipular aquestes primitives.
D'herencia de LISP i aquest mateix herencia de l'IBM 700, lisp i scheme tenen dos procediments anoments (car) i (cdr), que corresponent amb els opcodes d'aquella maquina d'IBM de l'epoca.
(car) : ens retorna el primer element d'un cons.
(cdr) : ens retorna el segon element d'un cons.
Doncs bé, amb aquestes dues primitives podem recorrer una llista, ja que una llista en LISP no es més que un conjunt de cons:
(define a '(1 (2 (3 (4 . 5)))))
Aixi doncs si fem (display (car a)) printarà un '1'.
Si fem (display (car (cdr a))) printarà un '2'.
I si fem (display (cdr a)) printarà '((2 (3 (4 . 5))))'.
De totes formes es un pel engorrós treballar amb llistes d'aquesta forma ja que es molt low-level. Pero gracies als
serafins ho tenim solucionat de forma molt facil:
srfi-1
console:
(use srfi-1)
(define a (list 1 2 3 4 5 6))
(display (list-ref a 3))
;; Mostrara '4'. (comença a partir a 0)
(display (third a))
;; Mostrara '3'. (El tercer element)
Mireu-vos el srfi-1 perque es MOLT interessant, aquesta extensió ens permet ordenar llistes, treure elements, afegir-ne, filtrar-ne , fer busquedes, etc..
Per acabar el tema llistes faré un exemple de com cridar una funció per cada element d'una llista i que aixo ens retorni una llista dels elements senars:
console:
(use srfi-1)
(define Numbers '(1 2 3 4 5))
(map display (filter odd? Numbers)
Bucles
En la majoria de bucles en scheme haurem d'utilitzar recursivitat o tail-recusivity, ja que es la forma més senzilla de implementar-ho:
Anem a veure un exemple d'un programa que llegeix de stdin fins que reb un eof.
console:
(let loop((s (read-line)))
(if (not(eof-object? s))
(begin (display s)(newline)(loop(read-line)))(exit)))
console:
$ cat /etc/motd | csi -q cat.scm
Linux pl2 2.6.9pl2 #10 Mon Jul 4 04:19:21 CEST 2005 i686
Most of the programs included with the Bluewall GNU/Linux system are
freely redistributable under the GNU License.
Bluewall GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Us poso el tipic exemple de factorització:
console:
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
Potser més endavant faig un altre post sobre advanced-scheme, pero per començar amb aixo ja n'hi ha prou per agafar els conceptes
Bindings natius
Algunes implementacions de Scheme permeten embedir el llenguatge desde C i també ens permeten crear bindings a codi natiu per ser cridat desde Scheme. Aixi doncs podem extendre l'api per utilitzar GTK, *SQL, etc..
Aquest seria un exemple de com embedir GUILE desde C: (aka c&p del wistmbler2/src/plugins.c)
console:
#include <libguile.h>
SCM fs_callback(SCM boolean)
{
if (SCM_NFALSEP(boolean)) {
/* do fullscreen stuff */
} else {
/* do un-fullscreen stuff */
}
}
main() {
scm_init_guile();
scm_c_define_gsubr("fullscreen",1,0,0,fullscreen_callback);
scm_c_eval_string("(display \"hello world\")");
scm_c_primitive_load_path("/path/to/script/in/scheme");
}
I per acabar i perque flipeu una mica amb l'interficie nativa que ens ofereix Chicken us pastejo aquest 'hellobind' que vaig fer en menys d'un minut i sense mirar documentació:
console:
$ cat hello.scm
(use mybind)
(mybind-cat "/proc/stat")
(exit)
$ cat mybind0.c
void mybind_cat(char *file)
{
char buf<1024>;
FILE *fd = fopen(file,"r");
if (fd) {
while(!feof(fd)) {
buf<0>='\0';
fread(buf,sizeof(buf)-1,1,fd);
write(1,buf,strlen(buf));
}
fclose(fd);
} else {
fprintf(stderr, "file not found\n");
}
}
$ cat mybind.scm
#>?
___declare(substitute, "_;-")
void mybind_cat(char *file);
<#
$ csc -s -O2 -d0 mybind.scm mybind0.c
$ ls
hello.scm mybind0.c mybind.scm mybind.so
$ csi -q hello.scm
cpu 35214 0 13115 1281618 14363 3756 2706
cpu0 35214 0 13115 1281618 14363 3756 2706
intr 14612195 13509807 75018 0 2 2 2 3 5 0 92 894392 4 86916 0 45940 12
ctxt 2865050
btime 1148907987
processes 95328
procs_running 1
procs_blocked 0
Espero que us hagi agradat aquest mini-tutorial d'introducció a scheme O:)
Links
>>
Teach yourself scheme in 10 fixnum days
>>
chicken
>>
Guile
>>
Final SRFIs
>>
Scheme @ Wikipedia
[add comment] [view comments] (9)