Le avventure di un Pythonista in Schemeland /3

Nelle puntate precedenti mi sono limitato a discutere alcuni aspetti "di
contorno" del linguaggio Scheme: disponibilità di librerie,
affidabilità delle implementazioni, supporto in caso di bugs,
eccetera. In questa puntata invece affronterò alcune tematiche più
inerenti al linguaggio, come la sintassi, il modo di programmare e la
filosofia generale. Comincierò con una discussione delle famigerate
parentesi, che da sempre sono oggetto di innumerevoli discussioni:
dopotutto, come probabilmente saprete, LISP sta per
Lots of Irritating Superfluous Parenthesis, e Scheme ha ancor
più parentesi di altri Lisp!


Di parentesi e indentazione

Io ho fatto l’errore di provare a scrivere codice Scheme usando un
editor diverso da Emacs (lo scheme mode di default è pessimo secondo
me). È stato un suicidio. Dopo qualche settimana di sofferenza sono
tornato a Emacs, ho postato vari messaggi su comp.lang.scheme e comp.emacs
e scoperto che esiste un ottimo scheme mode, chiamato quack.el
e scritto da Neil Van Dike.
In più, ho scoperto come aumentare il contrasto delle parentesi e
adesso mi trovo benissimo. Ma ripeto che è un suicidio
provare ad editare codice Scheme senza un buon supporto dall’editor.
Secondo me è la prima ragione per cui legioni
di principianti (newbies) rifuggono da Scheme/Lisp: chi vuole essere costretto a
imparare Emacs solo per scrivere qualche riga di codice?
Certo, si potrebbe facilmente arguire che imparare Emacs è uno sforzo
che paga ma io non credo che si debba
essere obbligati a usare un tool altamente specifico per poter utilizzare
un linguaggio di programmazione di uso generale. Secondo me si dovrebbe
essere liberi di scegliersi l’editor che vuole ed essere in grado
di programmare perfino in Notepad (anche se è una cosa che non
auguro a nessuno! 😉

È interessante notare quello che dice sull’argomento
Paul Graham, un grande nome del
Lisp e autore di Arc, un nuovo linguaggio della famiglia del Lisp
da poco rilasciato ed implementato in PLT Scheme:

We also plan to let programmers omit parentheses where no ambiguity would
result, and show structure by indentation instead of parentheses.
I find that I spontaneously do both these things when writing Lisp by
hand on whiteboards or the backs of envelopes.

Paul Graham

Arc per il momento sembra richiedere le parentesi, ma ve ne
sono un pò meno che in Scheme, come potete vedere dal tutorial.
È chiaro che le parentesi NON sono indispensabili ed uno potrebbe
benissimo immaginare dei sistemi per ridurre il loro numero (io stesso ne
ho escogitati vari quando ho iniziato a programmare in Scheme).

Esiste anche un SRFI (SRFI-49: Indentation-sensitive syntax)
che propone di usare l’indentazione rispetto alle parentesi traendo
ispirazione da Python (!) La proposta va considerata come una
curiosità; discutere sulla scelta dell’indentazione poteva aver senso trent’anni fa,
quando Scheme è stato introdotto per la prima volta. Al giorno d’oggi,
quando il 100% del codice di Scheme è scritto con le parentesi, tanto
vale adeguarsi allo stile dominante. Il principiante sarebbe svantaggiato
imparando uno stile che nessuno usa.

Da come la vedo io, le parentesi costituiscono
una vera e propria prova di iniziazione: se un programmatore non riesce
a sopportarle allora non merita di programmare in Scheme/Lisp ed è meglio
che si butti su linguaggi inferiori (cioè tutti i linguaggi, almeno a
sentire loro). Devo dire comunque che a mio avviso l’atteggiamento snobista è più
comune nella comunità del Common Lisp che nella comunità di Scheme a cui il
newbie sta più a cuore. Sta di fatto che il sistema funziona e il
livello medio dei programmatori Scheme/Lisp è MOLTO elevato: solo
i più coriacei sopravvivono.

Da buon Pythonista io non credo che un linguaggio debba sfruttare
questi mezzucci: ogni linguaggio dovrebbe essere reso accessibile al più
largo pubblico possibile; poco importa se anche programmatori scarsi riescono
a capirlo, questo non è uno svantaggio, semmai è un pregio! La presenza
di programmatori scarsi aumenta anche il numero di posti di lavoro, perché
serve qualcuno che corregga i loro errori. Altrimenti non si spiegherebbe
il numero di offerte di lavoro in Java e C++ ! (ok, è una battuta, ma
passatemela, alla fin fine contiene un fondo di verità 😉

Alla fine, comunque, quando dopo molte sofferenze uno ha imparato a
destreggiarsi con le parentesi, non torna più indietro: a patto di
aver padroneggiato un buon editor ci sono grossi vantaggi nella
scrittura del codice. Uno per tutti: l’indentazione automatica. Mai
più sezioni di codice spedite via e-mail e rese illeggibili da macelli
con l’indentazione causati da Outlook Express; mai più energie spese
per a a reindentare codice in fase di refactoring.
Certo, ci sono sempre difficoltà e
può capitare di dimenticare qualche parentesi aperta,
oppure di chiuderne più del dovuto: ma tutto sommato sono disposto a
riconoscere che sul lungo termine le parentesi pagano. Ma senz’altro
non pagano sul breve periodo e non sono affatto convinto che il sacrificio
imposto ai newbies sia giustificato: non sarebbe costato molto introdurre
una sintassi con meno parentesi, da usare agli inizi o nei
casi in cui non si avesse a disposizione un buon editor. Naturalmente
ormai è troppo tardi per tornare indietro: nel bene o nel male Scheme
è un linguaggio pieno di parentesi e già che ci sono tanto vale goderne
i vantaggi.

Della sintassi prefissa

A questo punto è giunto il momento di dire qualche parola su
un’altra peculiarità di Scheme/Lisp, la sintassi prefissa:
in Scheme non si scrive 1+1 come tutti abbiamo imparato a fare fin dalle
elementari, si scrive (+ 1 1) invece. In altre parole, l’operatore
+ va messo all’inizio, come prefisso, e non in mezzo. A differenza delle
parentesi (che potrebbero essere ridotte), la sintassi prefissa non mi ha mai
dato nessun problema di principio, visto che si tratta di qualcosa
di perfettamente consistente: anche in Python in realtà si scrive
prima la funzione e poi gli argomenti. Per esempio l’espressione 1+1
di fatto è zucchero sintattico per int.__add__(1,1) quindi la notazione prefissa
non dovrebbe risultare affatto sorprendente per un Pythonista.

Quello che dà fastidio è il
fatto che non ci sia una maniera standard in Scheme per semplificare
la scrittura di formule matematiche: non sarebbe costato assolutamente
nulla inserire una macro in grado di convertire la sintassi usuale
(detta sintassi infissa) in sintassi prefissa, qualcosa del tipo

(with-infix a+b*c) => (+ a (* b c))

Gli autori di Scheme, probabilmente per motivi pedagogici,
hanno preferito non inserire una macro del genere nello standard, per
obbligare gli studenti a scriversela da soli. Ma chi studente non è,
non apprezza molto questa scelta, che comunque è indicativa della
differenza tra Python e Scheme: Python eccelle nel rendere semplici
le cose di uso comune, mentre Scheme non ci prova neppure.

D’altra parte, la sintassi prefissa ha enormi vantaggi quando si
fanno entrare nel gioco le macro. La regolarità assoluta dei programmi
Scheme/Lisp, che sono sequenze di s-espressioni della forma

(nome argomenti ...)

dove gli argomenti possono essere a loro volta espressioni nidificate
della forma (altro-nome altri-argomenti ...) rende la generazione automatica
di codice una tecnica efficacissima. Per capire questo punto
dovrete attendere per qualche puntata, quando introdurrò le macro: ma posso
già anticipare che il codice Scheme non è pensato per essere scritto
da umani, è pensato per essere scritto automaticamente dalle macro
.
Soltanto quando avrete capito questo punto capirete che le parentesi sono
un bene. Io ci ho messo qualche mese a capirlo, alcuni non lo capiscono
mai e abbandonano Scheme disgustati.

Se riuscirete a superare la prova di iniziazione vedrete che
le s-espressioni (che sono solitamente ma non necessariamente
associate alle parentesi) hanno un loro senso. Una volta che
lo si è capito la notazione tradizionale (infissa) diventa penosa e più
di ostacolo che di aiuto. Inoltre l’uniformità assoluta di
Scheme ha in sè una innegabile bellezza ed eleganza. Niente
sintassi inutile, niente boilerplate code, si respira un’aria
di minimalismo Zen.

Dove si mostra un esempio di programma in Scheme

Dopo tanto parlare, lasciatemi finalmente dare un breve esempio di
programma in Scheme. Tradizionalmente, l’esempio che si dà è
quello del calcolo di un fattoriale:

Exlamation
(define (fac x)
   (if (= x 0) 1
    (* x
     (fac (- x 1)))))

(define n (string->number (car (reverse (argv)))))
(display (fac n))

che tradotto in Python sarebbe

import sys

def fac(x):
  if x == 0:
    return 1
  else:
    return x * fac(x-1)

n = int(sys.argv[-1])
print fac(n),

Già questo esempio banale dimostra ampiamente quanto ho discusso
finora:

  1. Ci sono un sacco di parentesi: cinque parentesi alla fine del fattoriale,
    e quattro alla fine della definizione di n. Un programma tipico contiene
    3-4 parentesi per riga. È da notare che quasi tutte queste parentesi sono
    inutili: usando l’SRFI-49 il codice precedente potrebbe benissimo
    essere scritto come:

    define fac
       if (= x 0) 1
        * x
         fac (- x 1)
    
    define n
     string->number
      car (reverse argv)
    display (fac n)
    
  2. Il programma è assolutamente non portabile: funziona in Chicken
    ma in nessun’altra implementazione di Scheme.

    Il motivo è che lo standard R5RS NON SPECIFICA nessun metodo per
    leggere gli argomenti della linea di comando, quindi argv non è
    standard.

    Agli occhi di un Pythonista una mancanza del genere è assurda, ma
    è solo dopo trent’anni di vita che gli "Schermidori" hanno fissato
    come gestire sys.argv nello standard R6RS, che è ancora
    molto poco diffuso.

  3. Per ottenere l’ultimo elemento della lista argv, Python una la sintassi
    standard argv[-1]; non esiste una sintassi standard per fare ciò
    in Scheme, quindi o si usa una funzione non portabile, oppure per prendere
    l’ultimo elemento della lista la si rovescia con reverse e si
    prende il primo elemento con car (se volete conoscere l’origine
    del termine vi rimando a questo articolo di Wikipedia).
    Alcune implementazioni di
    Scheme accettano anche il sinonimo più leggibile first, ma
    non tutte. La leggibilità non conta secondo gli ideatori di Scheme.

  4. Il risultato di fac dipende dall’implementazione: alcune implementazioni
    supportano i numeri ad infinita precisione, altre no: in particolare in Chicken
    si ottiene

CHICKEN
Version 2.732 - macosx-unix-gnu-x86     manyargs dload ptables applyhook cross
(c)2000-2007 Felix L. Winkelmann        compiled 2007-11-01 on michele-mac.local (Darwin)
#;1> (define (fac x)  (if (= x 0) 1 (* x (fac (- x 1)))))
#;2> (fac 10)
3628800
#;3> (fac 100)
9.33262154439441e+157
#;4> (fac 1000)
+inf

mentre in Ikarus (che è R6RS-compliant, dunque supporta i numeri a infinita
precisione) si ottiene

Ikarus Scheme version 0.0.2
Copyright (c) 2006-2007 Abdulaziz Ghuloum
> (define (fac (x) (if (= x 0) 1 (* x (fac (- x 1))))))
> (fac 10)
3628800
> (fac 100)
93326215443944152681699238856266700490715968
264381621468592963895217599993229915608941463
976156518286253697920827223758251185210916864
000000000000000000000000
> (fac 1000)
4023872600 ... < moltissime altre cifre>

A leggere queste prime tre puntate può venir voglia
di lasciar perdere tutto: suppongo che i lettori che sono arrivati
fin qui abbiamo sentito questa domanda frullare nella loro testa per tutto
il tempo: ma vale davvero la pena di passare attraverso questa via Crucis?
Probabilmente per la maggior parte dei lettori la risposta è no. Ma io
mi rivolgo ai lettori testardi e persistenti, e spero bene di riuscire
a far vedere qualcosa di positivo nella prossima puntata.
Abbiate fiducia e arrivederci!

Comments

  1. Fino a questo momento, sembra di muoversi all’interno di Lisp.. e devo dire che ho sempre mal digerito l’uso insano delle parentesi, sebbene mi sia divertito parecchio a lavorare con CAR e CDR..

    Bellissima questa serie di articoli!

  2. Walter Franzini says:

    Per quello che riguarda l’inserimento delle parentesi in Emacs si puo` utilizzare la combinazione M-( che inserisce nel testo una coppia di parentesi e posiziona il cursore in mezzo. In questo modo dovrebbe essere piu` difficile avere parentesi non bilanciate.

    Si puo` anche attivare show-paren-mode che evidenzia le parentesi che “corrispondono”.

    La serie e` molto interessante, spero che arriverai anche a parlare di tail-recursion (che credo di aver compreso) e di “continuazioni” (che proprio non ho capito).

  3. Michele Simionato says:

    @Walter: per la tail recursion si tratta di aspettare
    pochissimo, apparira’ nella prossima puntata.
    Per le continuazioni invece bisognera’ avere
    pazienza, ma se il pubblico continua ad essere
    fedele prima o poi ci arriveremo.

  4. Molti lispers/schemers usano vim.
    Esistono poi degli ide:

    http://www.plt-scheme.org
    schemeway.sourceforge.net
    http://www.jazzscheme.org

  5. Non sarei cosi’ severo a definire “mezzucci” le difficolta’ che puo’ incontrare un newby in Scheme rispetto a Python.

    E’ una scelta di fondo diversa, ben spiegata da Peter Norvig:

    “Python has the philosophy of making sensible compromises that make the easy things very easy, and don’t preclude too many hard things.
    […]
    Lisp has the philosophy of making fewer compromises: of providing a very powerful and totally consistent core.”

    La facilita’ di Python non e’ gratis.

  6. Per chi volesse invocare a linea di comando un file scheme in ikarus (sotto cygwin):

    $ ikarus --r6rs-script fac.ss 5
    120
    $ cat fac.ss
    (import (rnrs))
    (define (fac x)
       (if (= x 0) 1
        (* x
         (fac (- x 1)))))
    
    (define n (string->number (car (reverse (command-line)))))
    (display (fac n))
    
  7. Andrea says:

    Forse overkill, ma per editare codice Lisp-like in Emacs esiste paredit.el ^__^;

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.