← torna

Llenguatges de programació

Els llenguatges de programació són l'eina que fem servir per donar instruccions als ordinadors. N'hi ha molts, de tots tipus i colors, però només uns pocs han sigut capaços d'innovar.

En aquest article, vull explorar-los. L'important aquí, però, no són els llenguatges concrets, sinó les idees que representen. És com el número 1: et pots inventar mil maneres de dibuixar-lo, que el concepte d'unitat seguirà sent el mateix.

Per a cada llenguatge, l'exemple serà:

Llenguatge d'assemblador

El llenguatge d'assemblador és una fina capa de traducció damunt del codi màquina que executa l'ordinador. És essencialment un sistema d'etiquetes, de mnemotècnia. Aquest llenguatge, que t'obliga a especificar cada petita acció que executa el processador, era l'única manera de treure tot el suc dels primers ordinadors i consoles.

No existeix un únic llenguatge d'assemblador, ja que cada arquitectura de processadors té el seu propi set d'instruccions. L'exemple que veurem és dissenyat per al processador 6502, el xip darrere de sistemes tan emblemàtics com l'Atari 2600, la Commodore 64, l'Apple II o la NES.

lda #0
sta $f0
ldx #1

loop:
  lda $f0
  stx $f1
  adc $f1
  sta $f0
  inx
  cpx #$b
  bne loop
    

Cada línia comença amb una instrucció, representada per tres lletres, seguida d'un paràmetre opcional.

Imagina't que el processador és un escriptor amb una estranya obsessió per l'organització. En el seu despatx, hi ha una prestatgeria plena de blocs de notes i una taula d'escriptori. Cada vegada que vol escriure alguna cosa, l'escriptor agafa un bloc de notes, el posa damunt la taula, hi escriu quelcom, i el torna a desar allà on estava. Això és anàleg al que fa un processador: agafa un valor de la memòria, el posa en un registre, hi fa els càlculs pertinents, i desa el resultat a la memòria.

El xip 6502 tan sols té tres registres que podem manipular:

Per a entendre l'exemple, aprendrem algunes instruccions:

lda ? load A copia un valor (?) al registre A
ldx ? load X copia un valor (?) al registre X
sta ? store A desa el valor d'A a la memòria (?)
stx ? store X desa el valor d'X a la memòria (?)
adc ? add w/ carry suma el valor d'A i el paràmetre (?)
inx increment X suma 1 al valor d'X
cpx ? compare X compara el valor d'X amb el paràmetre (?)
bne ? branch if not equal si la darrera comparació no és zero, ves a l'etiqueta (?)

Podem indicar els paràmetres amb dos prefixos:

Si no indiquem un nombre com a literal, s'entén que el paràmetre fa referència al valor desat a la memòria que indica el nombre. Així doncs, #$b ens indica un valor d'11, mentre $b ens indica el valor que trobarem a la posició 11 de la memòria (sigui quin sigui). D'això se'n diu l'adreça de memòria.

Així doncs, podem tornar a veure el codi. (tot el que està després de ';' són comentaris, i l'assemblador els ignora)

lda #0      ; A ← 0
sta $f0     ; desa A a l'adreça $f0
ldx #1      ; X ← 1

loop:
  lda $f0   ; A ← el valor de l'adreça $f0
  stx $f1   ; desa X a l'adreça $f1
  adc $f1   ; suma A + el valor de l'adreça $f1, desa el resultat a A
  sta $f0   ; desa A a l'adreça $f0
  inx       ; incrementa X (+1)
  cpx #$b   ; comprova si X és $b (=11)
  bne loop  ; si no ho és, ves a 'loop:' i continua l'execució des d'allà
    

Finalment, tindrem el resultat (55) a l'adreça $f0.

BASIC

El BASIC (Beginner's All-purpose Symbolic Instruction Code) és un llenguatge dissenyat per ser fàcil d'usar. Creat originalment el 1964 per John G. Kemeny i Thomas E. Kurtz, a la Universitat de Dartmouth, l'objectiu era que tots els estudiants poguessin escriure programes, fossin o no d'àrees tècniques.

En BASIC, en lloc de gestionar memòria i registres, el programador dona les instruccions amb fórmules matemàtiques i estructures de control senzilles.

Cada línia comença amb un nombre, que va incrementant-se (en general, per 10). Això està pensat per escriure el programa de manera interactiva, línia a línia. D'aquesta manera, si introduïm la línia 20 i 30, i després volem afegir-ne una enmig, podem usar el 25.

Al llarg del temps, moltes empreses i persones individuals han creat les seves versions de BASIC, algunes amb extensions més avançades. Per al nostre exemple, m'he basat en el Sunflower BASIC.

10 LET I:1
20 LET X:0
30   LET X:X+I
40   LET I:I+1
50   IF I<11 GOTO 30
60 PRINT X
70 END
    

Cada línia conté una sentència. En aquest exemple en tenim quatre tipus:

LET nom : expressió associa el resultat d'una expressió (fórmula) a un nom
IF condició GOTO línia si la condició es compleix, ves a la línia
PRINT expressió mostra per pantalla el resultat d'una expressió
END finalitza l'execució

A més a més, podem afegir comentaris amb la sentència REM (remember).

Primer, inicialitzem dues variables: I, que serà l'índex que incrementarem d'1 a 10, i X, que tindrà el total de la suma.

10 LET I:1
20 LET X:0
    

Després tenim el bucle. Afegim el resultat d'X+I a la variable X, i fem l'increment d'I. Si el valor d'I és inferior a 11, l'execució torna a la línia 30, si no ho és, continuarem el programa més avall.

30   LET X:X+I
40   LET I:I+1
50   IF I<11 GOTO 30
    

Finalment, mostrem per pantalla (imprimim) el valor d'X i aturem l'execució.

60 PRINT X
70 END
    

El BASIC és una eina educativa excel·lent, gràcies a la seva senzillesa i claredat. Evidentment, ningú escriurà el proper joc AAA en BASIC, però la seva importància en la computació no es pot subestimar.

Forth

El Forth és un llenguatge concatenat basat en una memòria en pila, creat per Chuck Moore als anys 60. "Un llenguatge què...?" diràs.

Ens endinsem en un paradigma de programació no gaire conegut. I per fer-ho, primer hem d'entendre dos conceptes: la concatenació i la memòria en pila.

La concatenació és la idea d'aplicar funcions una rere l'altra. És la diferència entre dir: "suma 1 al resultat de multiplicar 2 per 3", en lloc de "pren 2, multiplica-ho per 3, suma-li 1". L'estil concatenat és com una recepta, un seguit d'instruccions que s'han de fer una a una.

Una de les característiques més importants de la concatenació és que podem extreure seqüències d'instruccions i referenciar-les diferents vegades: d'això se'n diu refacció. Per exemple, partim de la següent frase: "La meva tieta Pepa és una senyora gran. A la meva tieta Pepa li encanten les taronges. És per això que sempre que veig a la meva tieta Pepa, li'n porto." Uf, horrible. Ho podem solucionar fent facció, extraient la seqüència "la meva tieta Pepa" reemplaçant-ho per "ella"/"la". Així doncs: "Ella és la meva tieta Pepa. Ella és una senyora gran. A ella li encanten les taronges. És per això que sempre que la veig, li'n porto."

Nota de llengua: Sí, ho sé, tieta no apareix al DIEC. Quin horror. Tampoc hi apareix panqueque.

Una memòria en pila és una estructura de dades. Els valors s'apilen un damunt de l'altre, però nosaltres només podem accedir al valor de damunt cada vegada. És com una pila de panqueques. Pots anar afegint panqueque sobre panqueque, però només podràs menjar-los un per un, agafant el panqueque de damunt. Això ens permet estructurar una seqüència d'operacions i fer càlculs. Per exemple, la seqüència "1 2 3 * 4 + +":

pila operació descripció
1 posa 1 al damunt de la pila
1 2 posa 2 al damunt de la pila
1 2 3 posa 3 al damunt de la pila
1 2 3 * treu dos nombres de la pila, multiplica'ls, i posa-hi el resultat
1 6 4 posa 4 al damunt de la pila
1 6 4 + treu dos nombres de la pila, suma'ls, i posa-hi el resultat
1 10 + treu dos nombres de la pila, suma'ls, i posa-hi el resultat
11

Aquesta manera d'escriure operacions matemàtiques s'anomena notació polonesa inversa. Pot semblar una bogeria, però ens permet prescindir de parèntesis i de l'ordre de les operacions.

notació regular notació polonesa inversa
1 + 2 * 3 1 2 3 * +
(1 + 2) * 3 1 2 + 3 *
1 * 2 + 3 1 2 * 3 +
1 * (2 + 3) 1 2 3 + *

Combinant la concatenació i la memòria en pila tenim el Forth. Vegem l'exemple:

: add 1 + 0 swap 1 do i + loop ;

10 add .
    

Qualsevol grup de lletres o símbols separat per espais és una paraula en Forth. La forma ": paraula ... ;" defineix una nova paraula. Així doncs, en la primera línia, estem definint la paraula add.

Podem reemplaçar la paraula add per la seva definició:

10 1 + 0 swap 1 do i + loop .
    

Ara analitzem-ho pas a pas:

pila paraula descripció
10 posa 10 al damunt de la pila
10 1 posa 1 al damunt de la pila
10 1 + treu dos nombres de la pila, suma'ls, i posa-hi el resultat
11 0 posa 0 al damunt de la pila
11 0 swap treu dos nombres de la pila, intercanvia'ls, i torna'ls a posar
0 11 1 posa 1 al damunt de la pila
0 11 1 do inicia un bucle: treu dos nombres, un màxim (11) i un índex (1)
0 i posa l'índex del bucle damunt de la pila
0 1 + treu dos nombres de la pila, suma'ls, i posa-hi el resultat
1 loop si l'índex = el màxim, atura el bucle, sinó incrementa l'índex i torna a "do"

L'execució continua, posant l'índex a la pila, sumant-lo i comprovant si hem arribat al màxim; si no hi hem arribat, incrementa l'índex i tornem-hi. Finalment, la paraula "." treu un nombre de la pila (el resultat de sumar 1 a 10) i el mostra per pantalla.

El paradigma de programació concatenat és poètic. No necessita variables, no necessita un codi estructurat, només una seqüència de paraules i una pila... és com escriure una simfonia per a un flabiol.

Lisp

El Lisp (list processing) és un llenguatge funcional, basat en una estructura de llistes, originalment desenvolupat per John McCarthy el 1958.

Aquest llenguatge és infame pels parèntesis, però aquesta és part de la seva estructura cristal·lina. En Lisp (o qualsevol altre llenguatge funcional) no li diem a l'ordinador com fer quelcom, sinó què. És com definir una fórmula matemàtica.

El problema que volem resoldre és la suma dels nombres de l'1 al 10. Generalitzem a l'interval de l'1 a N, on N és un nombre > o = 1. En el cas més senzill, el cas base, N és 1. Així doncs, f(1) = 1. Si N = 2, podem definir-ho com a 2+f(1), és a dir, N+f(N-1). I si N = 3? Funciona igual: f(3) = 3+f(3-1); f(3-1) és f(2) = 2+f(2-1); f(2-1) és f(1) = 1. Hem definit una funció a partir d'ella mateixa, i d'això se'n diu recursivitat.

Ara, mira el codi i fixa't com s'emmiralla en la fórmula que acabem de definir:

(defun f (n)
  (if (= n 1)
      1
      (+ n (f (- n 1)))))

(f 10)
    

El primer element de la llista és la funció, de manera que (f 10) és f(10). Amb defun definim la funció f amb un paràmetre n. Si (= n 1); és a dir, si n=1, la funció és 1. Si no, la funció és (+ n (f (- n 1))); és a dir, n+f(n-1).

Malgrat que la sintaxi de (parèntesis (dins (de (parèntesis)))) no acaba d'agradar a tothom, molts dels desenvolupaments que va encapçalar el Lisp han acabat influenciant altres llenguatges enormement populars, com Python i JavaScript. Una d'aquestes innovacions, que no hem vist aquí, són les funcions d'ordre superior: funcions que prenen com a paràmetre altres funcions! Seria com tenir f(+,10) i f(*,10), una mateixa funció que serveix tant per sumar com per multiplicar els nombres de l'1 al 10.

APL

L'APL és un llenguatge orientat a vectors, desenvolupat per Kenneth E. Iverson als anys 60. Això significa que es basa en vectors multidimensionals: llistes de llistes de llistes... de matrius... de nombres...

Aquest és un paradigma força específic, però que té un ús especialment important en la computació científica, quan estem tractant amb grans quantitats de dades. Els llenguatges orientats a vectors també són especials en poder resumir a la mínima expressió el codi per a resoldre certs tipus de problemes.

+/⍳10
    

Sip, això és tot. Literalment 5 caràcters. Analitzem.

Podem visualitzar el procés en dos passos:

      ⍳10
1 2 3 4 5 6 7 8 9 10
      +/⍳10
55
    

Si canviem la funció de suma per la multiplicació, tindrem el factorial de 10 (10!).

      ×/⍳10
3628800
    

Curiós, oi?

L'APL sovint es veu com un grapat de jeroglífics incomprensibles (ex. una funció per calcular la mitjana seria {(+⌿⍵)÷≢⍵}), però la veritat és que no amaga tanta complexitat. Simplement, fa servir símbols poc convencionals i molta informació concentrada.

Pascal

El Pascal és un llenguatge imperatiu i estructurat, desenvolupat per Niklaus Wirth a finals dels anys 60. Seguint l'objectiu de facilitar la programació, però sense limitar el potencial del llenguatge, el Pascal va arribar a ser un dels (o el) llenguatge de programació més popular als anys 70. Ara bé, com que el sistema operatiu UNIX (i derivats com Linux i maxOS) es va escriure en C, el Pascal va passar a un segon pla.

Això no treu que no sigui un llenguatge molt capaç i relativament fàcil d'entendre.

var x : integer;
    i : integer;
begin
    x := 0;
    for i := 1 to 10 do
        x := x + i;
    Write(x)
end.
    

Hi ha una clara separació entre la declaració de les variables i l'execució del programa. Declarem les dues variables (i, x) com a nombres enters. A més a més, les estructures de control són clares: per i := 1 fins a 10 fes... Estableix clarament el bucle, amb l'índex de l'1 al 10.

C

El C és un llenguatge imperatiu i procedural, desenvolupat per Dennis Ritchie i Ken Thompson als Laboratoris Bell d'AT&T als anys 70.

És l'avi de tota la computació moderna. Tots els xips tenen un compilador de C. Tots els sistemes tenen peces essencials escrites en C. Es considera el llenguatge més ràpid. Etc.

El C també és la lingua franca dels llenguatges de programació. Vols interactuar amb el nucli de Linux? Ho has de fer amb C. Amb el de Windows? C (... o C++).

int x = 0;
for (int i = 1; i <= 10; i++) {
    x += i;
}
printf("%d", x);
    

La sintaxi en C és més esquemàtica que el Pascal (i similars). Fem servir {claus}, punts i coma; i seqüències misterioses de caràcters "%d". Aquesta barreja d'elements, però, és un petit preu a pagar per l'enorme poder que t'atorga. Amb C pots fer-ho tot, controlar cada bit.

Ara bé, un gran poder comporta una gran responsabilitat. Amb C pots escalfar la casa, però també incendiar-la.

Lua

El Lua (lluna en portuguès) és un llenguatge imperatiu i estructurat, dissenyat per Robert Ierusalimschy, Luiz Henrique de Figueiredo i Waldemar Celes, com a membres del grup del Tecgraf a la universitat PUC-Rio de Brasil, el 1993. Pensat en un començament com a una eina de configuració en els programes interns de l'empresa de petroli nacional (Petrobras), el Lua ha aconseguit convertir-se en el llenguatge de guió (o script) per excel·lència.

Això és degut a la seva facilitat per incorporar-se a programes escrits amb C. A més a més, la seva sintaxi clara i senzilla i l'ús de les taules com a estructura única han contribuït a la seva popularitat. Molts videojocs l'usen per a programar la lògica interna de la història, els personatges, l'entorn... o per fer mods.

x = 0
for i = 1, 10 do
    x = x + i
end
print(x)
    

Encara que no és present a l'exemple, he de mencionar les taules. Aquesta és l'estructura única del Lua, que combina un vector normal (una llista d'elements) amb un d'associatiu (un grup d'elements, cadascun identificat amb un nom). Es construeix amb {claus}.

t = {
  1, 2, 3, 4,
  nom = "Joan"
}
    

La implementació oficial es basa en una màquina virtual, de manera que podem executar un mateix codi Lua en diferents sistemes, sense haver-hi un procés de compilació (traduir-lo al codi màquina).