Visualizzazione post con etichetta programmazione. Mostra tutti i post
Visualizzazione post con etichetta programmazione. Mostra tutti i post

10 settembre, 2020

REPL un'idea che ha cambiato l'informatica

 


Una cosa che avrei voluto fare da piccolo era affiancare il Lisp al Fortran; cosa impossibile, anzi impensabile, ancora adesso è pericoloso nominarlo. Ma sono vecchio e chissene!

Una cosa che in qualche misura --mica ne sono addicto nèh!-- è la storia, non universale ma quella limitata all'informatica, hardware e software; non tutto, solo quello con cui ho avuto (o avrei voluto) a che fare.

L'altro giorno mi capita che --questo lo racconto dopo, prima l'ambiento.

Non sono interessi solo miei, anzi! Per esempio su Twitter Duncan McGregor rilancia un tweet di tanto tempo fa, questo.

Il link originario non c'è più ma seguendo il thread si arriva a una meraviglia, il PDF di The LISP Implementation for the PDP-1 Computer di L. Peter Deutsch e Edmund C. Berkley, roba di DEC, marzo 1964, troppo presto allora ero alle medie.

Nota: L Peter Deutsch rockz! 💥 Davvero.

Bellissimo, c'è tutto, chiaro, conciso, bastano una quarantina di pagine. Non parlo del linguaggio (anche se ...) ma della novità del cap. 6. Input and Output: l'interattività.

È sempre  Oubiwann  Duncan che lancia questo 3D (si capisce che sta per thread?) che --ve lo dicevo che non è una fissa solo mia-- "'ve had a heck of a time determining when the first true LISP REPL was implemented"

Salto ma uh! questo lo ricordo, cioè non lui ma un rifacimento in Basic su Prime, fine anni '70, questo


io l'avevo anche tradotto in italiano; siccome il DB (un semplice file di testo) cresceva con le risposte dopo un po' era tutto in italiano. No, non c'era il web, non l'ho diffuso, chissà se c'è ancora qualche nastro... No, il Prime non c'è più, come neanche DEC (Digital Equipment Corporation).

OK, sto divagando ma tanti dei vecchi la REPL l'hanno conosciuta e usata con il Basic, probabilmente il GW-Basic se su IBM (o compatibile) o qualche altra implementazione.

Roba di fine anni '60. Non solo Basic, ovviamente; per esempio bc (1975) e prima ancora il suo antenato dc ("dc is the oldest surviving Unix language" dice la Wiki).

Quelli seri (o solo i vecchi?) allora non usavano il PC, non usavano linguaggi interpretati (cioè, a dirla tutta, usavano il Fortran). Non immaginavano che il futuro sarebbe stato quello che poi è stato. E i linguaggi interpretati avrebbero vinto, confinando i compilati in alcuni settori, non che non siano fondamentali ma è comunque una riserva. E oggi è normale, per esempio Python:

$ python3 -q
>>> 3 + 5
8
>>> a = 8 + (3 + 4) * 5 - 1
>>> a
42
>>>
$

o Node.js, come dire JavaScript:

$ node
> 3 + 5
8
> a = 8 + (3 + 4) * 5 - 1
42
> $

o Racket, evoluzione di Scheme, evoluzione del Lisp:

$ racket
Welcome to Racket v7.8.
> (+ 3 5)
8
> (define a (- (+ 8 (* (+ 3 4) 5)) 1))
> a
42
> ^D
$

Anzi, no: le REPLs ci sono ma si usano ambienti inegrati, IDEs molto più user friendly (io no, ma solo perché sono vecchio).

L'altro giorno salta fuori che Bash, la solita shell, la REPL... Si può fare, ecco un template, fa poco,  upcasizza  maiuscolizza, dipende tutto dall'eval.

n=0
while read -ep "$n > " line; do
    if [[ $line == "q!" ]]; then
        echo 'bye!'
        exit 0
    fi
    echo ${line^^}
    (( n++ ))
done

$ bash repl.sh
0 > esempio di REPL
ESEMPIO DI REPL
1 > fa poco, è un template
FA POCO, È UN TEMPLATE
2 > (si esce come nel vi)
(SI ESCE COME NEL VI)
3 > q!
bye!
$

Idea 💡, forse:

$ eval r=$(( 5 + 4 ))
$ echo $r
9
$

No, meglio Excel (o bc, per me).
🔴🔵

03 gennaio, 2020

Codice vecchio


Uh! capitato di nuovo! poche righe da aggiungere ma ... <PanicAssay!>; dai fatto in fretta e funziona (almeno da me). Adesso racconto.

C'è un mio script (principalmente mio, con contributi di terzi) del 2016 che funziona benissimo ma...
sarebbe ancora più pratico se si potessero specificare alcune opzioni, il DB è cresciuto, lungo a scorrerlo ogni volta; sì lo so che già c'è una scelta ma andrebbe raffinata, anche rispetto altri parametri...
ahemmm... si può fare; almeno ci provo.

Ci sono un paio di vincoli:
  • non modificare i moduli (sono diversi, ognuno con pochissime funzioni);
  • non toccare il DB;
  • ricorda che qui gli scripts si richiamano l'un l'altro.
Davvero tre anni fa (quasi quattro) ho scritto roba così? Io? Beh, pare di sì. E non si può riscrivere, solo aggiungere modifiche compatibili con l'esistente.

Ci ho messo un po' di tempo a capire come funziona, a colpi di print, il debug (sono vecchio) lo uso solo per le grandi occasioni. Poi tutto OK, facile. A dire il vero un pasticcio l'ho fatto (e purtroppo non riesco a riprodurlo) e Python mi indicava l'istruzione sbagliata quale responsabile dell'errore, qualcosa dovuto a un cast fatto male (int o str); corretto, devo essere meno pasticcione.

Però davvero ultimamente il mio codice è molto migliorato. O comunque cambiato. Un po' (anche se non totalmente) così 🙂


🔴🔵

17 settembre, 2019

Visibilità (scope) delle variabili - 1


Continuo da qui la serie di post elementari sui linguaggi con visibilità delle variabili lessicale e/o dinamica. Però ormai parlo di un argomento collegato, più personale, probabilmente interessa solo me.

Ah! una cosa in parte OT: mi sono letto i documenti citati in bibliografia da Ming-Ho (non tutti, confesso), avendo tempo ci sono cose interessanti, qualcuna la sapevo, altre meno. Ma il solito problema: il tempo.

Questo post arriva in cascata alla serie ma è diverso, forse sono solo io che ho fatto confusione, voglio chiarirmi un po' meglio come Python e JavaScript considerano le variabili, globali e locali. Un post molto semplice, didascalico, solo per me (probabilmente, come già detto).

Normalmente le variabili (numeriche, vedremo prossimamente che per gli oggetti valgono altre leggi) sono passate come valore alle funzioni e nel chiamante il loro valore non muta. Esempio, in Python:

def f():
    print('f()', x)
    return x

def g(n):
    x = n
    print('g()', n, f())
    return f()

# main
x = 1
print('start x =', x)
print(x, f(), g(2), x, f())
x = 5
print('cambio x =', x)
print(x, f(), g(3), x, f())


$ py3 s-n.py
start x = 1
f() 1
f() 1
g() 2 1
f() 1
f() 1
1 1 1 1 1
cambio x = 5
f() 5
f() 5
g() 3 5
f() 5
f() 5
5 5 5 5 5
$


Stesso comportamento con JavaScript con l'avvertenza di definite con var le variabili locali, altrimenti sono globali:

function f() {
    console.log('f()', x)
    return x
}

function g(n) {
    var x = n
    console.log('g()', n, f())
    return f()
}

// main
x = 1
console.log('start x =', x)
console.log(x, f(), g(2), x, f())
x = 5
console.log('cambio x =', x)
console.log(x, f(), g(3), x, f())


$ node s-n.js
start x = 1
f() 1
f() 1
g() 2 1
f() 1
f() 1
1 1 1 1 1
cambio x = 5
f() 5
f() 5
g() 3 5
f() 5
f() 5
5 5 5 5 5
$


OK, finora tutte le variabili sono locali (in realtà c'è la sola x in g()); Per operare con variabili globali in Python uso l'instruzione global globalmente, cioè esterna alle funzioni. Ma in questo caso il comportamento è diverso da come potrebbe apparire (è un errore!): la x in g() è locale:

global x

def f():
    print('f()', x)
    return x

def g(n):
    x = n
    print('g()', n, f())
    return f()

# main
x = 1
print('start x =', x)
print(x, f(), g(2), x, f())
x = 5
print('cambio x =', x)
print(x, f(), g(3), x, f())


$ py3 s-ge.py
start x = 1
f() 1
f() 1
g() 2 1
f() 1
f() 1
1 1 1 1 1
cambio x = 5
f() 5
f() 5
g() 3 5
f() 5
f() 5
5 5 5 5 5

$

Risulta che il valore di x in g() non ha influenza su f() perché questa è globale e non relativa a g().

Per chiarire uno script ancora più focalizzato

global x
x = 1

def g(n):
    print(x) # <- errore qui, x non è definita
    x = n
    print(x)

#main
g(2)
print(x)


eseguendola si ottiene un errore:

$ py3 gl.py
Traceback (most recent call last):
  File "gl.py", line 10, in <module>
    g(2)
  File "gl.py", line 5, in g
    print(x)
UnboundLocalError: local variable 'x' referenced before assignment
$


commentando la print iniziale in g() risulta come in g() x sia locale:

$ py3 gl.py
2
1
$

In JavaScript, dove le variabili sono globali di default:

function f() {
    console.log('f()', x)
    return x
}

function g(n) {
    x = n // modif qui, x è globale
    console.log('g()', n, f())
    return f()
}

// main
x = 1
console.log('start x =', x)
console.log(x, f(), g(2), x, f())
x = 5
console.log('cambio x =', x)
console.log(x, f(), g(3), x, f())


$ node s-ge.js
start x = 1
f() 1
f() 2
g() 2 2
f() 2
f() 2
1 1 2 2 2
cambio x = 5
f() 5
f() 3
g() 3 3
f() 3
f() 3
5 5 3 3 3

$

OOPS! in questo caso la x in g() determina il valore di f(). Ho cioè un comportamento diverso per i due linguaggi, cosa che almeno per me induce errori se non si presta la dovuta attenzione. (Sì, sono ripetitivo).

Da sempre si usano variabili globali ma per quanto è possibile queste vanno limitate per quanto possibile. La variabile x in g() che voglio globale la dichiaro global all'interno della funzione; la chiamata a g() sarà diversa a quella ad h() dove x è locale. Peraltro diventa importante l'ordine delle chiamate:

def f():
    print('f()', x)
    return x

def g(n):
    global x
    x = n
    print('g()', n, f())
    return f()

def h(n):
    x = n
    print('h()', n, f())
    return f()

# main
x = 1
print('start x =', x)
print(x, f(), g(2), x, f())

x = 5
print('cambio x =', x)
print(x, f(), g(3), x, f())

x = 8
print('cambio x =', x)
print(x, f(), h(4), g(3), x, h(6), f())


$ py3 s-gl.py
start x = 1
f() 1
f() 2
g() 2 2
f() 2
f() 2
1 1 2 2 2
cambio x = 5
f() 5
f() 3
g() 3 3
f() 3
f() 3
5 5 3 3 3
cambio x = 8
f() 8
f() 8
h() 4 8
f() 8
f() 3
g() 3 3
f() 3
f() 3
h() 6 3
f() 3
f() 3
8 8 8 3 3 3 3

$
JavaScript, per quanto precedentemente detto è in questo caso più intuitivo

function f() {
    console.log('f()', x)
    return x
}

function g(n) {
    x = n // modif qui, x è globale
    console.log('g()', n, f())
    return f()
}

function h(n) {
    var x = n // x è locale
    console.log('h()', n, f())
    return f()
}

// main
x = 1
console.log('start x =', x)
console.log(x, f(), g(2), x, f())
x = 5
console.log('cambio x =', x)
console.log(x, f(), g(3), x, f())
x = 8
console.log('cambio x =', x)
console.log(x, f(), h(4), g(3), x, h(6), f())


$ node s-gl.js
start x = 1
f() 1
f() 2
g() 2 2
f() 2
f() 2
1 1 2 2 2
cambio x = 5
f() 5
f() 3
g() 3 3
f() 3
f() 3
5 5 3 3 3
cambio x = 8
f() 8
f() 8
h() 4 8
f() 8
f() 3
g() 3 3
f() 3
f() 3
h() 6 3
f() 3
f() 3
8 8 8 3 3 3 3
$


OK, ma le liste? Uh! per le liste (gli oggetti) questo non vale, come vedremo prossimament, non cambiate canale 🙂
🔴

14 settembre, 2019

Visibilità (scope) lessicale e dinamica - 3


Continuo da qui la serie di post elementari sui linguaggi con visibilità delle variabili lessicale e/o dinamica.

Però questo è più una diversione su come venivano definite le variabili, globali rispetto alle locali quando ho cominciato, tanto tempo fa. Mi riferisco al Fortran ((IV o 66) o 77) con l'avvertenza che allora avrei dovuto scrivere tutto maiuscolo, tranne qualche eccezione nel 77 (p.es. Apollo). Allora i sistemi operativi erano tanti, diversi e altrettante le varianti per i compilatori. Occorreva adeguarsi alla macchina e evitare le particolarità; per me adeguarsi a DEC, quelli del VAX. Benché il linguaggio fosse vecchio di più di 20 anni era di gran lunga il più usato e lo sarebbe rimasto ancora per parecchio tempo anche se insidiato dal BASIC (sui PC) e dal C (su Unix).

Il programmino proposto da Ming-Ho, l'avrei scritto così:

      program ming_ho
      call setx(1)
      print *, ig()
      end

      function jf(ia)
      common /cox/ ix
      jf = ix + ia
      end

      function ig()
      ix = 2
      ig = jf(0)
      end

      subroutine setx(n)
      common /cox/ ix
      ix = n
      end


Forse è il caso di commentare il codice; anzi devo premettere che appena iniziata la lettura del post di Ming-Ho mi è subito venuto da pensare che le ambiguità sulla visibilità (scope) delle variabili nel Fortran non c'era. Cioè non c'erano le variabili globali, sono tutte locali. Ma possono essere condivise, con i common. Non che il common non potesse generare bugs ma questo sarebbe un altro discorso (lungo).
Ah! una nota: ho cambiato il nome alle variabili e funzioni per adeguarmi alla convenzione dei nomi impliciti, per gli interi premetto una i ai nomi di Ming-Ho; la funzione f() la rinomino in jf() per evitare amibuità con if, cosa che sarebbe possibile (non esistono parole riservate in Fortran) ma che potrebbe rendere quixotica la comprensione del codice.

L'esame de codice rivela una prima particolarità che lo distingue dagli altri linguaggi usati allora (C e Pascal): il main, il programma principale è di solito all'inizio, eventualmente indicato dall'istruzione program che ne definisce il nome.
Nel mio caso il main è composto da una chiamata alla subroutine setx che setta il valore alla variabile globale (cioè in common) che verrà utilizzata dalla funzioni if(). Segue l'istruzione di scrivere il valore della funzione ig(). end termina il main; potrei scrivere anche end nome-del-programma ma nessuno lo faceva, neache per funzioni e subroutines.

In questo caso l'istruzione call setx(1) potevo sostituirla con la coppia common /cox/ ix seguita da ix = 1. In un programma più complesso una routine di settaggio delle variabili globali acquista senso e oggi faccio il didascalico. Notare che posso scrivere common /cox/ n e poi n = 1, l'importante è di inserire il valore 1 nei primi 4 bytes di cox. Inoltre il common potrebbe essere senza nome, così common n. setx non deve ritornare un valore e allora conviene definirla come subroutine e quindi chiamarla con call.

La funzione jf() usa la variabile ix memorizzata nel common cox. A questa variabile somma l'argomento ia e questa somma è il valore ritornato. Notare la particolarità: si usa il nome della funzione per indicare il valore della funzione. Esiste anche l'istruzione return ma non ritorna niente; indica l'abbandono della funzione (o subroutine) e il ritorno al chiamante.

Nella funzione ig() setto la variabile ix ma questa è locale e non verrà utilizzata; anzi probabilmente il compilatore eliminerà questa istruzione (non ho verificato, una volta, sulla la mia macchina era così). ig() ritorna poi la chiamata a jf() con argomento 0. Non agisco sulla variabile nel common e quindi qui il common non compare.

La subroutine setx assegna il valore passato come argomento al common. Essendo una subroutine non torna un valore al chiamante.

Non resta che provare il programma (il file ld.f produrrà l'eseguibile ld); lo compilo ed eseguo ottenendo:

$ gfortran -o ld ld.f
$ ./ld
           1
$


OK, vero che è più semplice? o sono solo io.
🔴

13 settembre, 2019

Visibilità (scope) lessicale e dinamica - 2


Continuo dal post precedente, e --forse-- altri seguiranno sull'argomento.

Una correzione al suddetto post: la pic che ho ottenuto da Ming-Ho non era psceudocodice ma codice vero in R, linguaggio che non ho mai frequentato (solo una volta ci sono passato vicino ma è una storia personale (e dimenticata)).

Intanto è arrivata la continuazione promessa da Ming-Ho, ben 2 post: New blog post by @mhyee Scoping in R.

Ming-Ho entra nel dettaglio del codice proposto e ci illustra come in R lo scope sia --come dire-- non tanto semplice: "All of this might make you a little uncomfortable, and uncertain about R’s scoping rules". Chiaro, esaustivo ma non quello che m'interessa.
E ci rimanda alla continuazione (terzo post, il secondo della giornata) dove "I’d like to discuss a paper by the creators of R, where they motivate the need for lexical scoping in a statistical programming language. This is a “bonus” blog post, because I’m going to dive into some of the hairier R features to show how four different kinds of scoping can be simulated in R". Esaustivo, @mhyee l'ho followato su Twitter, rockz! 💥

Io invece mi sono dedicato a linguaggi di script che uso (o ho usato in passato) dinamici. Espongo quanto trovato, confessando che non ne ero pienamente cosciente finora.

bc
bc lo uso da sempre, anche se ultimamente molto meno.

#!/usr/bin/bc

x = 1
define f(a) { return x + a }
define g() {
    x = 2
    return f(0)
}
print(g())
print "\n"
quit


$ bc dl.bc
2
$


Come si vede è dinamico, le variabili sono globali ma c'è l'opzione auto che le rende locali. Il risultato però non cambia resta dinamico, ossia nel caso in questione f() usa il valore corrente di x:

#!/usr/bin/bc

x = 1
define f(a) { return x + a }
define g() {
    auto x /* <- x locale */
    x = 2
    return f(0)
}
print g(), "\n"
print "(x = ", x, ")\n"
quit


$ bc dla.bc
2
(x = 1)
$


Calc
Calc - C-style arbitrary precision calculator è un tool cui sono affezionato anche se --ammetto-- ultimamente frequento di meno, mi sono costruito un piccolo sostituto in Python.

#!/usr/bin/calc

x = 1
define f(a) {
    return (x + a)
}
define g() {
    /* local x; */
    x = 2;
    return f(0)
}
print g(), "\n( x = ", x, ")"


$ calc -f ld.cal
2
( x =  2 )
$


Scommnentando l'istruzione commentata x diventa locale e lo scope diventa lessicale:

$ calc -f ld.cal
1
( x =  1 )
$


newLISP
Altro linguaggio di scripting che sto trascurando. Pare non solo io e nel mio caso c'è anche uno cui posso dare la colpa Racket. newLISP ha dei punti di forza, è piccolo, veloce, potente, ... Ma ci sono tanti Lisp (e successori) e --secondo me-- Racket vince anche per il supporto. Ma intanto ecco lo script:

#!//usr/bin/newlisp

(set 'x 1)
(define (f a) (+ x a))
(define (g)
  (set 'x 2)
  (f 0))
(println (g))
(println "x = " x)
(exit)


$ newlisp ld.nl
2
x = 2
$


OK, dinamico, come da manuale, ma è possibile renderlo lessicale (non l'ho fatto ma è immediato).

OK, resto dell'idea che il lexical scope sia migliore anche se per script molto piccoli l'importanza è relativa. Ma devo leggermi quanto segnalato da Ming-Ho. E, inoltre, non so se confessare un pensiero che mi è venuto fin da subito, una cosa che però rivelerebbe quanto sono vecchio. Ma questo nella continuazione. Probabilmente. Forse 🙂
🔴

07 settembre, 2019

Gestire condizioni di errore


Forse è solo una cosa per noi vecchi, i giovani sono abituati a un ambiente integrato, guidato, dove questi errori non possono capitare. A meno che... Ecco parlo (solo un cenno) di quando un'elaborazione è composta di più programmi concatenati da eseguire in sequenza. E (giustamente) si automatizza.

Tento di farla semplice, la realtà può essere molto più articolata, i dati possono arrivare dal web e certe valutazioni temporizzate o quando si supera una soglia predefinita o altre condizioni ancora. Tutte cose che salto, mi concentro solo su un argomento semplicissimo: l'exit-code che ogni programma ritorna (o dovrebbe tornare).

Un esempio --il minimo che sono riuscito a pensare-- è eec.py, legge dati e qui può insorgere un errore e li elabora dove non tutto può essere come previsto:

#!/usr/bin/python3

from sys import argv

def leggi_input():
    global n1, n2
    try:
        n1 = int(argv[1])
        n2 = int(argv[2])
    except:
        print('Errore di input')
        exit(1)

def rapporto():
    global n1, n2
    print(n1, n2)
    try:
        return n1 / n2
    except:
        print('Errore nel rapporto')
        exit(1)

# main
leggi_input()
r = rapporto()
print(n1, n2, r)


Provandolo si ha:

$ py3 eec.py 10 3
10 3
10 3 3.3333333333333335
$ py3 eec.py 10
Errore di input
$ py3 eec.py 10 0
10 0
Errore nel rapporto

OK, in caso di errore exit interrompe l'esecuzione e setta l'exit code a 1. Se tutto va come previsto il codice ritornato è 0, a essere davvero puntiglioso occorrerebbe inserire come ultima istruzione exit(0), come si fa con il C, ma Python lo fa automaticamente:

Help on built-in function exit in module sys:

exit(...)
    exit([status])

    Exit the interpreter by raising SystemExit(status).
    If the status is omitted or None, it defaults to zero (i.e., success).
    If the status is an integer, it will be used as the system exit status.
    If it is another kind of object, it will be printed and the system
    exit status will be one (i.e., failure).


Una cosa che non sono riuscito a trovare è la differenza tra le due diverse funzioni exit() di Python: c'è quella predefinita, usata nell'esempio, un'altra nel modulo sys. L'help riportato sopra è quello ottenuto con help(sys.exit) ma vale anche per l'exit() predefinito.

Nota: se status è una stringa il codice di uscita è sempre 1, (file ecs.py):

#!/bin/bash

from sys import argv

ec = argv[1]
print('Esco con:', ec)
exit(ec)


ottengo:

$ py3 ecs.py 0
Esco con: 0
0
$ echo $?
1
$ py3 ecs.py 10
Esco con: 10
10
$ echo $?
1
$ py3 ecs.py orrore!
Esco con: orrore!
orrore!
$ echo $?
1

OK, resta da vedere il lato bash (o altro equivalente). La versione naïf non funziona (tec.sh):

#!/bin/bash

python3 eec.py $1
echo "exit code =" $?
echo "e adesso continuo..."


$ bash tec.sh 10 3
10 3
10 3 3.3333333333333335
exit code = 0
e adesso continuo...
$ bash tec.sh 10
Errore di input
exit code = 1
e adesso continuo...
$ bash tec.sh 10 0
10 0
Errore nel rapporto
exit code = 1
e adesso continuo...

La correzione richiesta è semplice (tecs.sh):

#!/bin/bash

set -e

python3 eec.py $1 $2
echo "exit code =" $?
echo "e adesso continuo..."


$ bash tecs.sh 10 3
10 3
10 3 3.3333333333333335
exit code = 0
e adesso continuo...
$ bash tecs.sh 10
Errore di input
$ bash tecs.sh 10 0
10 0
Errore nel rapporto

OK 🤩👌

C'è ancora un altro caso che finora non abbiamo risolto: la pipe, vero che c'è pipefail e PIPESTATUS ma, nonostante le googlate, niente finora 👿
🔴

26 luglio, 2019

10 PRINT CHR$(205.5+RND(1)); : GOTO 10

Una cosa che avevo già visto ma di corsa (manca sempre il tempo) e c'è voluto il suggerimento di Mariano Tomatis, the Wonder Injector, a farmelo apprezzate come si deve.

Tutto qui, 95 secondi densi; da rivedere più volte. Notare che lo schermo è attuale ma il 'puter è quello che dice, il Commodore 64.

Il libro cui fa riferimento è distante un hoogle, qui: 10 PRINT CHR$(205.5+RND(1)); : GOTO 10. E in questa stessa pagina c'è il link al PDF liberamente scaricabile, lo faccio subito-subito.

Il volume fa parte di una serie dedicata al software: Software is deeply woven into contemporary life—economically, culturally, creatively, politically—in manners both obvious and nearly invisible.

This book returns to a moment, the early 1980s, by focusing on a
single line of code, a BASIC program that reads simply:

10 PRINT CHR$(205.5+RND(1)); : GOTO 10

One line of code, set to repeat endlessly, which will run until interrupted.


Segue una descrizione dettagliata che salto ma ne approfitto per qualche considerazione strettamente personale. Io allora ero già indaffarato nel lavoro, su Prime, minicomputer simile al più noto PDP-11. Per i giovani una precisazione: "mini" significa grosso come un armadio, era inteso relativo ai mainframes; non dico delle caratteristiche tecniche perché non sarei creduto. E sì, in Fortran, c'era solo quello (praticamente).

La rivoluzione dei personal come il Commodore 64 e gli altri raccontati nel libro me la sono persa tutta fino all'arrivo del PC IBM (e compatibili, di marca e assemblati (si capisce ancora questo termine?)).

Ripensandoci dopo è stato come quella volta dell'asteroide sui dinosauri. Cambiato tutto anche il linguaggio. Il nuovo era --grosso "ohh!" per me-- il BASIC.

Salto, si può (deve) leggere nel book originale, molto dettagliato. Salto anche tutto quanto detto sui labirinti (dei due tipi, in italiano c'è quella distinzione?) ma apprezzo la figura 20.2 a p.42 raffigurante [i]nformation theory pioneer Claude Shannon pictured ca. 1950 with his mechanical mouse Theseus and its magnetic metal maze. Bellissima.

A p.52 si parla di port, adattamento di un programma a un sistema differente, cosa che allora era un'attività normale per i i mini, ognuno aveva un OS proprio e anche i linguaggi di programmazione dovevano essere adattati. Per esempio con il Fortran le unità (canali?) di I/O non erano predefinite; e le connessioni venivano stabilite all'esterno del programma --roba che oggi risulta difficilmente comprensibile.

Ma il port di cui tratta il nostro caso è più generale, e affrontato per le macchine comparabili al Commodore 64. Per Apple "Applesoft BASIC is one of two standard BASIC implementations for the Apple II; Applesoft is the one that supports floating point math and seems very similar to Commodore 64 BASIC. The Apple II family of computers was of the same era and uses the same processor as did the Commodore 64, the MOS 6502. Applesoft BASIC, like Commodore 64 BASIC, was written by Microsoft and based on its 6502 BASIC, a version [...] that derives from Microsoft’s Altair BASIC. The Apple II computers and the Commodore 64 were really quite alike, almost as if they were siblings separated by corporate circumstance". Ma i caratteri da stampare sono diversi, non c'è l'estensione all'ASCII oltre 127 del PETSCII. E "[a]lthough the “/” and “\” characters on Apple II computers are exactly diagonal, they do not span the entire square that bounds a character". c'era anche "the TRS-80 Color Computer (or “CoCo”), sold through Radio Shack" con ancora una diversa variante, dovuta alla funzione RND.

Esistono ovviamente versioni nei linguaggi contemporanei, tre sono riportate nel testo ma più interessanti quelle disponibili nel Web.

Io avevo già scritto una versione Python che poi ho scoperto versioni molto simili online (mi hanno pre-copiato!). E al posto di "/\" (quest'ultimo da escapare) si può/deve usare "\u2571\u2572".
Ma meglio partire da qui, qui e qui.

Salto a p.76 dove si parla di Joseph-Marie Jacquard e il suo telaio programmabile, antenato di nastri e schede perforate, vedi figura 30.9 a p.77 per un esempio esagerato; le schede IBM per il Fortran (vecchie e in disuso quando ho cominciato a lavorare) erano molto più semplici. Ma questo non riguarda il Basic, anzi ecco qui (io lo scopro adesso) il vero aspetto rivoluzionario del Basic.

I computers (commodore 64, Apple I e II e simili) sono descritti per quanto riguarda le caratteristiche dell'output in "The Computer Screen", p.85. A me interessa un altro aspetto: [e]arly interaction with computers happened largely on paper: on paper tape, on punch cards, and on print terminals and teletypewriters, with their scroll-like supplies of continuous paper for printing output and input both [...]. The standard output devices for computers through much of the 1970s were print terminals and teletypes. Output was not typically produced on pages of the sort that come from today’s laser printers, but on scrolls of standard or thermal paper. The form factor for such output was not a standard 81/2 × 11-inch page, but an essentially endless scroll that was typically 80 columns wide.

Adesso devo tenermi forte, ci arrivo con 40 anni di ritardo:

Teletypes were used to present the results of the first BASIC programs written at Dartmouth in the 1960s, and they were the typical means of interacting with important early programs such as Eliza and Adventure. With such a system for output, there was no need for an automated means of saving and viewing the “scrollback” --a user could actually pick up the scroll of output and look at it. Of course, this sort of output device meant that animation and other effects specific to video were impossible.
Why are typed line numbers required at all in a BASIC program? Programs written today in C, Perl, Python, Ruby, and other languages don’t use line numbers as a language construct: they aren’t necessary in BASIC either, as demonstrated by QBasic and Visual Basic, which don’t make use of them. If one wants a program to branch to a particular statement, the language can simply allow a label to be attached to the target line instead of a line number. Where line numbers particularly helped was in the act of editing a program, particularly when using a line editor or without access to a scrolling full-screen editor. The Commodore 64 does allow limited screen editing when programming in BASIC: the arrow keys can be used to move the cursor to any visible line, that line can be edited, and the new version of the line can be saved by pressing RETURN. This is a better editing capability than comes standard on the Apple II, but there is still no scrollback (no ability to go back past the current beginning of the screen) in BASIC on the Commodore 64. Line numbers provide a convenient way to get back to an earlier part of the program and to list a particular line or range of lines. Typing a line number by itself will delete the corresponding line, if one exists in memory. The interactive editing abilities that were based on line numbers were well represented even in very early versions of BASIC, including the first version of the BASIC that ran on the Dartmouth Time-Sharing System. Line numbers thus represent not just an organizational scheme, but also an interactive affordance developed in a particular context.
Ecco: la quantità di memoria estremamente ridotta, la mancanza di un editor e un OS estremamente ridotto tutti motivi che portano alla soluzione (ottima, lo ammetto del BASIC). Anche se --no questo lo dico dopo; probabilmente; forse.

Ovviamente il libro contiene molte altre cose, tante, troppe anche per solo elencarle in questo post. Solo un'eccezione: l'Atari 2600, non sapevo delle sue particolarità; un suo successore, l'ST diventò l'utensile indispensabile per compositori musicali computerizzati --OK, sono OT con ricordi perso.

Gli autori tutti rockzs! 💥 anche se sono solo Basic, si parla dei personal di quell'era e allora è giusto così.

Però --torno a dirmi-- altrove si faceva in modo diverso, completamente. E --ma sono solo io che mi sono perso quell'avventura-- il Basic e i programmatori Basic come dice EWD the teaching of BASIC should be rated as a criminal offence: it mutilates the mind beyond recovery.

Peraltro negli stessi anni diceva: FORTRAN, 'the infantile disorder', by now nearly 20 years old, is hopelessly inadequate for whatever computer application you have in mind today: it is now too clumsy, too risky, and too expensive to use. E: In the good old days physicists repeated each other's experiments, just to be sure. Today they stick to FORTRAN, so that they can share each other's programs, bugs included. Qui si sbagliava.
🔴

08 luglio, 2019

Calculemus! -- sì, ma forse anche no

Cosa non si trova ontehtoobz! in un tweet oggi questo:


Non so voi ma io  casi come questo mi trovo leibniziano.

Numeri grossi, enormi. Io che sono vecchio subito-subito si accende il *warning* per gli interi [-2^15 : 2^15 - 1]. Poi realizzo che è cosa passata, procedo con la REPL degli usuali (per me) linguaggi preferiti.

E siccome sono pigro invece di mettermi a scrivere codice ricorro a cheat.sh di Igor Chubin, rockz! 💥, devo saperne di più, intanto lo seguo su Twitter.

Racket

$ cht racket factorial
#|
 | traditional recursive factorial and tail-recursive factorial
 | ...https://stackoverflow.com/.../traditional-recursive-factorial-and-
 | tail-recursive-factorial
 |
 | You could use trace (http://docs.racket-
 | lang.org/reference/debugging.html#%28part._.Tracing%29).
 |#

#lang racket

(require racket/trace)

(define factorial
  (lambda (n)
    (if (= n 0)
        1
        (* n (factorial (- n 1))))))
(trace factorial)
(factorial 5)
;; Output:
;; >(factorial 5)
;; > (factorial 4)
;; > >(factorial 3)
;; > > (factorial 2)
;; > > >(factorial 1)
;; > > > (factorial 0)
;; < < < 1
;; < < <1
;; < < 2
;; < <6
;; < 24
;; <120
;; 120

(define factorial-tail
  (lambda (n acc)
    (if (= n 0)
        acc
        (factorial-tail (- n 1)  (* acc n )))))
(trace factorial-tail)
(factorial-tail 5 1)
;; Output:
;; >(factorial-tail 5 1)
;; >(factorial-tail 4 5)
;; >(factorial-tail 3 20)
;; >(factorial-tail 2 60)
;; >(factorial-tail 1 120)
;; >(factorial-tail 0 120)
;; <120
;; 120

; [Greg Hendershott] [so/q/22935169] [cc by-sa 3.0]

$

 
Vado...

$ rkt
Welcome to Racket v6.11.
> (define factorial-tail
    (lambda (n acc)
      (if (= n 0)
          acc
          (factorial-tail (- n 1)  (* acc n )))))

> (define num (* (factorial-tail 20 1) (factorial-tail 7 1)))
> num
12261826121210265600000
> (define den (* (factorial-tail 6 1) (factorial-tail 21 1)))
> den
36785478363630796800000
> (define r (/ num den))
> r
1/3
> (/ 207 621)
1/3
>

OK, avere il tipo razionale è un punto in più per Racket (anzi tutto il Lisp). E per valutare l'ordine di grandezza senza contare le cifre:

> (~r num #:notation 'exponential)
"1.226183e+22"
>

Python

$ cht python factorial
#  Function for Factorial in Python
#
#  Easiest way: math.factorial(x)
#  (https://docs.python.org/library/math.html) (available in 2.6 and
#  above).
#
#  If you want/have to write it yourself, use something like
#  (https://www.artima.com/forums/flat.jsp?forum=181&thread=75931)

def factorial(n):return reduce(lambda x,y:x*y,[1]+range(1,n+1))

#  or something more readable:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

#  As always, Google is your friend ;)
#
#  [schnaader] [so/q/5136447] [cc by-sa 3.0]

$


$
py3
>>> from math import factorial
>>> num = factorial(20) * factorial(7)
>>> num
12261826121210265600000
>>> den = factorial(6) * factorial(21)
>>> den
36785478363630796800000
>>> r = num / den
>>> r
0.3333333333333333
>>> r == (207 / 621)
True
>>>

Node.js --JavaScript

$ cht node factorial
/*
 * What is the fastest factorial function in JavaScript?
 *
 * Lookup table is the obvious way to go, if you're working with natural
 * numbers.
 * To calculate any factorial in real-time, you can speed it with a
 * cache, saving the numbers you've calculated before. Something like:
 */

factorial = (function() {
    var cache = {},
        fn = function(n) {
            if (n === 0) {
                return 1;
            } else if (cache[n]) {
                return cache[n];
            }
            return cache[n] = n * fn(n -1);
        };
    return fn;
})();

/*
 * You can precalculate some values in order to speed it even more.
 *
 * [xPheRe] [so/q/3959211] [cc by-sa 3.0]
 */
$
$ node
> factorial = (function() {
...     var cache = {},
...         fn = function(n) {
.....             if (n === 0) {
.......                 return 1;
.......             } else if (cache[n]) {
.......                 return cache[n];
.......             }
.....             return cache[n] = n * fn(n -1);
.....         };
...     return fn;
... })();

[Function: fn]
> num = factorial(20) * factorial(7)
1.2261826121210266e+22
> den = factorial(6) * factorial(21)
3.6785478363630797e+22
> r = num / den
0.3333333333333333
> r == (207 / 621)
true
>

Però seguire l'istinto a volte non è la cosa migliore, ripensandoci provo...

$ factor 207
207: 3 3 23

$
factor 621
621: 3 3 3 23

ecco c'è un 3 in più nei fattori del denominatore. Se poi provo a sviluppare i fattoriali ottengo:

numeratore
20! * 7! =
    20 * 19 * 18 * 17 * 16 * 15 * 14 * 13 * 12 * 11 * 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
*
    7 * 6 * 5 * 4 * 3 * 2 * 1

denominatore
21! * 6! =
    21 * 20 * 19 * 18 * 17 * 16 * 15 * 14 * 13 * 12 * 11 * 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
*
    6 * 5 * 4 * 3 * 2 * 1

eliminando i termini comuni si ha 7 al numeratore e 21 al denominatore.

Per gli altri casi proposti la semplificazione non è così immediata

$ rkt
Welcome to Racket v6.11.
> (/ 17556 55176)
7/22
> (/ 1500475 4741501)
25/79
>
$ factor 17556
17556: 2 2 3 7 11 19
$ factor 55176
55176: 2 2 2 3 11 11 19
$

$
factor 1500475
1500475: 5 5 47 1277

$
factor 4741501
4741501: 47 79 1277

$


entrano in gioco più fattori ma è la stessa cosa.

OK, le fonti, a me l'ha detto math prof che ha rilanciato (patata) Ichiro.

Chiaro, vero? 沢山のいいねとリツイートありがとうございます!

Resterebbe da chiarire quel ポテト. Ma bisognerebbe immergersi nella cultura jap, vedi il caso affine di 吉本 ばなな.
🔴

18 giugno, 2019

Parametri con nome e valore predefinito

tramonto marziano - Spirit

Con Python --OK, anche tanti altri linguaggi-- si possono facilmente definire le funzioni in modo da rendere flessibile il passaggio di parametri; capitano casi in cui questa opzione risulta molto conveniente e il codice risulta più chiaro.

Credo serva un esempio, minimo, così mi capisco anch'io. La funzione (è solo un esempio, nèh!) udc ritorna il valore delle unità u, decine d e centinaia c che le vengono passate; per un caso più realistico si può pensare agli angoli espressi come gradi, primi e secondi o al tempo dall'anno fino al secondo. Ecco la funzione udc:

def udc(u=0, d=0, c=0):
    res = u + d * 10 + c * 100
    return res


Ovviamente la variabile res è ridondante si possono (tranne eventualmente per i debug) unire le righe 2 e 3 nella seguente return u + d * 10 + c * 100. Anzi c'è un'unica istruzione e --volendo-- potrei scrivere udc = lambda u, d, c : u + d * 10 + c * 100. Ma no perché i parametri hanno valori di default. (C'è anche un altro motivo, non tutti usano (già) la notazione lambda, argomento da riparlarne in futuro (forse).)

Avendo valori predefiniti per i parametri posso usare la funzione diversi modi, eccone qualcuno:

>>> udc(1, 2, 3)
321
>>> udc(1, 2)
21
>>> udc(1)
1
>>> udc()
0
>>> udc(c=8)
800
>>> udc(c=8, d=4)
840


Tutto chiaro, vero?
  • chiamando la funzione con tutti gli argomenti si ha il risultato usuale;
  • passando un numero di argomenti minore di quelli dichiarati nella definizione questi vengono assegnati da sinistra;
  • posso anche chiamare la funzione senza argomenti;
  • posso chiamare la funzione con gli argomenti con nome.
Forse è inutile ricordare che in questo caso diventa ancora più importante dare nomi significativi alle variabili, ma lo sappiamo da sempre, vero?

I nomi dei parametri vengono riconosciuti solo nella chiamata alla funzione, se ho la variabile d e la uso per la chiamata è (ovviamente) solo un valore:

>>> d = 7
>>> udc(d)
7


risultato diverso da

>>> udc(d=7)
70


Ancora una cosa (ovvia): se uso nomi quelli devono essere:

>>> udc(k=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: udc() got an unexpected keyword argument 'k'


OT
: mi fanno notare che uso convenzioni mie non universali; per esempio è normale scrivere udc ( c = 7, d = 2 ), dipende dai gusti, secondo me è meno leggibile. Vale anche (e a volte serve) passare un argomento per riga:

>>> udc(d = 7,
...     c = 2)
270

🔴

05 giugno, 2019

eval() considerata pericolosa - non sempre vero


Io sono vecchio e alle volte non solo si vede ma trasmetto le mie abitudini anche a persone perfettamente normali, li travio. Adesso illustro un esempio, se del caso non leggete.

Serve intanto ambientare la scena: è quella in cui c'è un 'puter (non importa quale) ma non un telefono a portata di mano (o se c'è ignorarlo) e si è troppo pigri (lazy) per fare il calcolo con carta e penna. OK, c'è la calcolatrice, da qualche parte c'è l'icona o una voce di un menu o qualcosa di simile, ne sono quasi sicuro, anzi sicurissimissimo.

Però si può anche, più semplicemente (per noi vecchi), ricorrere a un qualsiasi linguaggio di programmazione, di quelli dinamici, interattivi, una volta sarebbe stato il Basic oggi --sai l'evoluzione... Siccome non sono razzista uso JavaScript:

* eval-arm $ node
> 37 * 73 / 13
207.76923076923077
>

Semplice vero? comodo vero? Quando una cosa l'hai fatta almeno un paio di volte --si dice-- la farai ancora e allora conviene automatizzarla. Che vorrebbe dire qualcosa così:

#!/usr/bin/node

expr = process.argv[2]
r = eval(expr)
console.log(r)


che si esegue così:

* eval-arm $ node xcjs "37 * 73 / 13"
207.76923076923077
* eval-arm $

Nel mio caso avevo inizialmente creato uno script che utilizzava bc, poi visto che c'è anche Windows e sono niubbo con Node.js sono passato a Python. Eccone una versione embrionale, elementare, un inizio da sviluppare:

#!/usr/bin/python3

from sys import argv
from math import e, pi, pow # e altre funzioni che voglio usare

if len(argv) <= 1:
    print("manca l'espressione")
    exit(2)

expr = argv[1]
expr = expr.replace('^', '**')

try:
    r = eval(expr)
except:
    r = expr + ": errore!"

print(r)


eseguo:

* eval-arm $ py3 xc.py "37 * 73 / 13"
207.76923076923077
* eval-arm $ py3 xc.py "TT 37 * 73 / 13"
TT 37 * 73 / 13: errore!
* eval-arm $


OK, funziona. E si possono fare calcoli più complessi:

* eval-arm $ py3 xc.py "pow(37 * 73, 1/2) / 13^2"
0.30752157302216054
* eval-arm $ py3 xc.py "pi^e - e^pi"
-0.6815349144182221
* eval-arm $


Notare che posso usare ^ per l'elevazione a potenza, è stata sufficiente 'istruzione expr = expr.replace('^', '**'). Resta da completare, inserire funzioni, sia di libreria che personali, aggiungere il formato per il risultato e altro ancora ma il più è fatto.

Però subito (anzi prima di subito (cit.)) salta fuori un'obiezione: l'uso di eval() è considerato cattiva programmazione.

Una googlata veloce ha dato una valanga (visto che non ho usato landslide?) di link:

MDN web docs, Moz://a
eval() is a dangerous function, which executes the code it's passed with the privileges of the caller. If you run eval() with a string that could be affected by a malicious party, you may end up running malicious code on the user's machine with the permissions of your webpage / extension. More importantly, a third-party code can see the scope in which eval() was invoked, which can lead to possible attacks in ways to which the similar Function is not susceptible.
eval() is also slower than the alternatives, since it has to invoke the JS interpreter, while many other constructs are optimized by modern JS engines.


Don't be eval()
JavaScript is an interpreted language, and like so many of its peers it includes the all powerful eval() function. eval() takes a string and executes it as if it were regular JavaScript code. It’s incredibly powerful and incredibly easy to abuse in ways that make your code slower and harder to maintain. As a general rule, if you’re using eval() there’s probably something wrong with your design.
Some programmers say that
eval() is B.A.D. – Broken As Designed – and should be removed from the language. However, there are some places in which it can dramatically simplify your code.

Why does eval() exist?
eval() exists because sometimes you want to give complete programmatic control of your application to code passed in at run time.

Why is using `eval` considered bad in JavaScript?
I heard the speaker in a meetup say, “You’re either evil, or just plain stupid if you use eval in your code”. Is there are any grain of truth behind the above statement?
I think it's one of those superstitious things that's based on truth where there is an element of truth to it, and because of this people avoid it religiously without understanding why. But just because somebody knows avoid it without knowing why doesn't mean they're wrong to avoid it. But it sure does make it difficult to ask questions about it and learn how to use it correctly.
It seems the only good use-case for eval() is interpreting JavaScript code contained within another language. Let's say you have some raw text, and some of that text is JS, you could use
eval() to turn the quoted JavaScript back into JavaScript and execute it. When that is the task you need to do, eval() is the only thing that will work, but it's very rare that you're needing to execute quoted JavaScript code from another language (and this should be done with caution when you do).

eval() isn’t evil, just misunderstood
Despite popular theory (and Crockford’s insistence), the mere presence of eval() does not indicate a problem. Using eval() does not automatically open you up to a Cross-Site Scripting (XSS) attack nor does it mean there is some lingering security vulnerability that you’re not aware of. Just like any tool, you need to know how to wield it correctly, but even if you use it incorrectly, the potential for damage is still fairly low and contained.

Is eval evil?
There is a high chance that you’ve stumbled across the JavaScript eval function.  It is a common knowledge that in JavaScript, eval is something that is a bad practice. Have you ever wondered how it works and why exactly a lot of people discourage it? This article covers that and presents some real-life usage of the eval function.
The
eval function takes the JavaScript code in the form of a string and evaluates it. It is quite a dangerous function to use. It is because of the fact it executes the code with the same privileges as the rest of the application. The code ran by eval also uses the same scope. If you use eval with a string that can be in any way affected by someone, you may face the harsh reality of it being malicious.

Eval is Evil, Part One
The eval method -- which takes a string containing JScript code, compiles it and runs it -- is probably the most powerful and most misused method in JScript. There are a few scenarios in which eval is invaluable.  For example, when you are building up complex mathematical expressions based on user input, or when you are serializing object state to a string so that it can be stored or transmitted, and reconstituted later.
However, these worthy scenarios make up a tiny percentage of the actual usage of
eval.  In the majority of cases, eval is used like a sledgehammer swatting a fly -- it gets the job done, but with too much power.  It's slow, it's unwieldy, and tends to magnify the damage when you make a mistake.  Please spread the word far and wide: if you are considering using eval then there is probably a better way.  Think hard before you use eval.

EVAL is evil? Using variables created dynamically - info retrieval
Uh! qui niente JS ma MATLAB; considerazioni interessanti, da leggere di là che non riporto.

Però tranne l'ultimo exhibit sono tutti relativi all'online (e Javascript) per ovvi problemi di sicurezza. Condizioni mooolto diverse dal caso qui considerato. E, al solito la Wiki la sa lunga (e ne ricorda l'origine: "Lisp was the original language to make use of an eval function in 1958. In fact, definition of the eval function led to the first implementation of the language interpreter". Ma se nomino il Lisp faccio scappare il lettore 🙁).

🔴

20 maggio, 2019

Evoluzione - archeologia

Un post non so quanto serio ma ci tengo. Racconta di me, dell'evoluzione dei linguaggi di programmazione (uno solo in realtà, il Fortran). Roba che oggi non si usa più, come il latino, per cui lo dico subito: caveat emptor (archeo-cit.).

Questa è l'ennesima riscrittura, grazie anche alle osservazioni di AP, ex collega (non vuole che lo si ricordi come boss), peccato non foglia comparire ontehtoobz.

L'idea iniziale era completamente diversa, partita da Compiler Explorer. Una piccola modifica al codice produce variazioni anche grandi nell'assembly; se poi si cambia linguaggio... Ecco, il Fortran, antichissimo, cambiato, evoluto nei suoi quasi 70 anni (proprio come cambia un umano) ma sempre vivo (forse, ancora qualcuno che lo usa?) o almeno compilabile.

Provo a scrivere --come sono capace, portate comprensione-- un programma banalissimissimo, la somma dei numeri naturali da 1 a 10 compreso. Roba che in Python si può scrivere così:

#!/usr/bin/python3

j = 0
for i in range(1, 11):
    j += i

print(j)

Eseguendo si ottiene:

$ python3 -q sum.py
55


(in realtà ho l'alias py3='python3 -q' ma non l'ho usato per chiarezza, spero verrà apprezzato come segno di buona volontà.)

Quando io ho iniziato a interagire con il computer, c'era già una storia, c'erano già dei vecchi. I vecchi, non tutti ma qualcuno, rifiutavano le cose troppo nuove, per esempio per fare il ciclo non usavano l'istruzione DO (in Fortran il for si chiama così) ma l'IF aritmetico, lo trovate qui a p.21. Sì, come leggere le tavolette d'argilla dei babilonesi; ma si può fare! (cit.)

Allora il contenuto delle schede perforate sarebbe stato qualcosa come questo:

C 34567
      I = 0
      J = 0

   10 CONTINUE
      I = I + 1
      J = J + I
      IF(I-10) 10, 20, 20
   20 CONTINUE

      WRITE(*, 90) J
   90 FORMAT(41HLA SOMMA DEI NUMERI NATURALI DA 1 A 10 È, I4)

      END


Come si usava allora scrivo (con un certo ribrezzo) tutto maiuscolo. Anzi no, ho fatto come al solito poi per la bella (questo post) ho convertito usando tr:

cat $* | tr [:lower:] [:upper:]

La prima riga è un commento (C in colonna 1) per agevolarmi a visualizzare i campi: commento in colonna 1, etichette (numeriche) nelle colonne [1:5], flag di continuazione in colonna 6 e codice nelle colonne [7:72].

Le variabili il cui nome inizia con [I:N] sono intere, float tutte le altre; è tuttavia possibile dochiararle di tipo diverso, ma i vecchi non avrebbero approvato.

L'istruzione WRITE scrive sull'unità predefinita (ahemmm... dipendeva dalla macchina, qui * è il terminale. Notare il formato Hollerith così chiamato in suo onore: dopo il carattere H ho il numero prima indicato di caratteri; oggi il compilatore accetta anche le lettere accentate!

Il ciclo è compreso tra le due istruzioni CONTINUE, gestito dalla (deprecata da prima che iniziassi) IF aritmetica che calcola l'espressione tra parentesi e salta alla label corrispondente a valore negativo, zero e positivo rispettivamente.

CONTINUE, usatissima allora è un segnaposto, non fa niente di suo ma serve per l'etichetta.

Semplice vero? Compilo ed eseguo:

$ gfortran -o f0 F0.f
$ ./f0
LA SOMMA DEI NUMERI NATURALI DA 1 A 10 È  55
$


Quando ho iniziato a mettere le mani sul 'puter (il PR1ME 550-II) si usava il Fortran IV, 1966. Anzi no: seguendo IBM ognuno si riteneva libero di apportare aggiornamenti allo standard vecchio ormai di di 15 anni; per il mio esempio non ne servono, ecco il Fortran IV:

      J = 0
      DO 10, I = 1, 10
          J = J + I
10    CONTINUE

      PRINT *, J

      END


Notare il ciclo DO identificato dall'etichetta 10. I vecchi avrebbero avuto da ridire per l'indentazione del blocco (qui una sola riga) ma penso che 1) il codice è più chiaro; e 2) oggi non mi diranno più niente.

Non sono sicurissimo se PRINT fosse standard, potrei andare a controllare, ma per edasso la uso.

Ottengo

$ gfortran -o f1 F1.f
$ ./f1
          55
$


OK. Nel '78 era uscito il nuovo standard, il Fortran 77 con parecchie nuove possibilità. Da me arrivò a inizio '80 ed era visto male perché esoso di risorse e poi if it's works don't fix it (cit.). Eccolo, sì in minuscolo:

      program sum10

      implicit none
      integer i, j

      j = 0
      do 10, i = 1, 10
          j = j + i
10    end do

      print *, j

      end


Dovrei controllare se implicit none era già standard o lo sarebbe diventato in seguito; raccomandato dai non-vecchi. program era un'istruzione solo estetica, il blocco del do conveniva chiuderlo con end do. Ah! dimenticavo: gli spazi non sono significativi per cui si possono mettere e togliere a piacimento, è valida enddo, come lo sarebbero altre in altri casi come goto e go to, da sempre. Ci sarebbe anche la cosa che zero è blank ma non voglio infierire.

Compilazione ed esecuzione lasciate come esercizio; passo alla versione '90.

Parecchie cose nuove, qui non le uso tranne una: il free format visto che le schede non le ricordava più nessuno. Una cosa sul do che credo valesse già nel '77 (o era un'estensione?): l'etichetta è opzionale e viene usata solo se condivisa da più cicli:

program sum10

implicit none
integer i, j

j = 0
do i = 1, 10
    j = j + i
end do

print *, j

end


Poi ho smesso, cioè no è il mondo che è cambiato. E continua a cambiare.

per esempio il codice Python usato all'inizio si potrebbe scrivere:

$ py3
>>> sum(range(1, 11))
55
>>>


dove uso la funzione predefinita sum(). Ma posso fare anche senza, per esempio, usando reduce():

>>> from functools import reduce
>>> reduce((lambda x, y: x + y), range(1, 11))
55
>>>


Uh!
sono di nuovo finito là; e mi sa che continua 🧐

🔴