Archivi Categorie: Linguaggi

Linguaggi di programmazione

Messaggio colorato nel terminale – 2

Diablefaucheur

Continuo con i messaggi colorati, completando questo post.

Oltre a Bash e Python manca qualcuno? In realtà molti ma in particolare JavaScript, per me Node.

Si può fare, anzi è una variante di quanto già visto. Va però considerato che JavaScript è nato e viene usato per il Web e solo raramente in locale per il terminale. Ma volendo… 😋

Il beep ha richiesto una sessione prolungata di  googlaggio  duckduckgoaggio, non funziona il solito \a delle origini di Unix. Anche perché a rappresenta “alarm” ma c’è dentro ^G. C’è il modulo beeper, ed ecco:

#!/usr/bin/node
var beeper = require('beeper');
beeper()

Il modulo dev’essere installato via npm. Dove abbiamo trovato l’indicazione di beeper? Purtroppo non lo abbiamo memorizzato. Ma guardando dentro si trova una soluzione più semplice, senza moduli aggiunti, molto simile alle versioni Bash e Pyton:

#!/usr/bin/node
console.log('\u0007');

eh sì! ^G.

Per il colore lo script (dis.js) è una variante di quello Python, dopo aver concatenato gli elementi della lista degli argomenti usa replace() per le sostituzioni. Notare la sintassi inusuale, derivata dalle regexps. Ed ecco:

#!/usr/bin/node

RED='\033[01;31m';
GREEN='\033[01;32m';
YELLOW='\033[01;33m';
BLUE='\033[01;34m';
ENDC='\033[00m';

args = process.argv;
n = args.length - 1;
i = 2
st = args[i];
while (i < n) {
    i += 1;
    st = st + ' ' + args[i];
}

st = st.replace(/%R/g, RED);
st = st.replace(/%G/g, GREEN);
st = st.replace(/%Y/g, YELLOW);
st = st.replace(/%B/g, BLUE);
st = st.replace(/%Z/g, ENDC);

console.log(st);

js

Messaggio colorato nel terminale – 1

kk2

Se un messaggio è importante deve attrarre l’attenzione. Argomento già trattato in passato ma questa è la versione 2018.

Ci sono due modi facili: un suono e un colore diverso dal solito.

Produrre un beep è semplice, così:

#!/bin/bash
echo -en "\a"

o così:

#!/usr/bin/python3
print("\a", end="")

Visualizzare il testo colorato è risultato immediato con Python, più impegnativo con Bash. I codici delle sequenze di escape ANSI si trovano (come farei senza la Wiki) qui: ANSI escape code – Colors.

La versione Python:

#!/usr/bin/python3

import sys

RED='\033[01;31m'
GREEN='\033[01;32m'
YELLOW='\033[01;33m'
BLUE='\033[01;34m'
ENDC='\033[00m'
BEEP='\a'

dis = str.join(' ', sys.argv[1:])

dis = dis.replace('%R', RED)
dis = dis.replace('%G', GREEN)
dis = dis.replace('%Y', YELLOW)
dis = dis.replace('%B', BLUE)
dis = dis.replace('%Z', ENDC)
dis = dis.replace('%_', BEEP)

print(dis)

 

I codici sono nella forma %L dove %L è una lettera (o anche più di una, o _ per il beep) che genera una sequenza improbabile: di solito % finisce la parola, terminando un numero.

Più impegnativo produrre l’equivalente Bash: abbiamo, PLG e io, provato con sed ma con pessimi risultati. Poi la dritta, da Stack Overflow ed ecco:

#!/bin/bash

st=$*
st=$(printf '%s' "$st" | sed -e 's/:R/\\033[01;31m/g')
st=$(printf '%s' "$st" | sed -e 's/:G/\\033[01;32m/g')
st=$(printf '%s' "$st" | sed -e 's/:Y/\\033[01;33m/g')
st=$(printf '%s' "$st" | sed -e 's/:B/\\033[01;34m/g')
st=$(printf '%s' "$st" | sed -e 's/:Z/\\033[00m/g')

echo -e $st

sh
Rispetto alla versione Python cambia il codice, uso : al posto di %. Probabilmente da uniformare. Prossimamente, forse, chissà 😐

log – qual è la base?

Recentemente mi sono imbattuto in quella che è diventata una delle mie ossessioni preferite. Adesso vi conto, così la supero, forse. C’era da indovinare come inserire i dati in una formula di cui avevamo una descrizione incompleta e una tabella di (pochi) valori. Si può fare? proviamo in due, indipendentemente, e otteniamo una buona correlazione ma con parametri differenti. Dal confronto salta fuori che è colpa del logaritmo. Cioè di che base usare.

Io in questi casi mi rivolgo alla Wiki. Ottima, esauriente, come di solito.

Ho chiesto agli amici di Facebook:

Se sono Veri Matematici è il logaritmo naturale, quindi in base e. Se c’è gente relativamente normale (relativamente, perché chi parla di logaritmi dal salumiere?) è il logaritmo in base dieci (e in questo caso il logaritmo naturale si scrive ln come sulle calcolatrici. Nota che gli informatici non parlano di logaritmo in base 2, perché dicono semplicemente bit.

e

Già, si dice che chi usa log per indicare logaritmo naturale poi usi Log per indicare il logaritmo in base 10, ma io non l’ho mai visto fare (perché chi usa log al posto di ln in realtà usa solo log, le altre basi sono per gli ingegneri).

Ah! sì, ricordo, quando usavo le calcolatrici, come nell’immagine lassù. E prima ancora il Bruhns per topografia (alle superiori ho frequentato l’istituto per geometri, il Guarini a Torino).

Ma me l’ero scordato; tutti (quasi) i linguaggi di programmazione usano base e. Ma allora perché a scuola (tutte?) si usa base 10? Non credo ci sia ancora chi li usi per moltiplicazioni, divisioni, radici ed elevazione a potenza. O anche qui sono male informato?

A proposito dei linguaggi c’è chi è davvero smart, ecco Python:

math.log(x[, base])

With one argument, return the natural logarithm of x (to base e).
With two arguments, return the logarithm of x to the given base, calculated as log(x)/log(base).

$ py3
>>> import math
>>> math.log(10)
2.302585092994046
>>> math.log(100)
4.605170185988092
>>> math.log(10, 10)
1.0
>>> math.log(100, 10)
2.0
>>> math.log(math.exp(1))
1.0
>>> math.log(9, 3)
2.0
>>> math.log(27, 3)
3.0
>>> math.log(42, 4)
2.6961587113893803
>>> 4**_
42.000000000000014

Bravo Python ma non è il solo, ecco Racket:

(log z [b]) → number

  z : number
  b : number = (exp 1)

Returns the natural logarithm of z. The result is normally inexact, but it is exact 0 when z is an exact 1. When z is exact 0, exn:fail:contract:divide-by-zero exception is raised.

If b is provided, it serves as an alternative base. It is equivalent to (/ (log z) (log b)), but can potentially run faster. If b is exact 1, exn:fail:contract:divide-by-zero exception is raised.

$ rkt
Welcome to Racket v6.11.
> (log 10)
2.302585092994046
> (log 100)
4.605170185988092
> (log 10 10)
1.0
> (log 100 10)
2.0
> (log (exp 1))
1.0
> (log 9 3)
2.0
> (log 27 3)
3.0
> (log 42 4)
2.6961587113893803
> (define le (log 42 4.2))
> le
2.6044944060210176
> (expt 4.2 le)
42.0

Per tutti gli altri la regola è semplice log_b(z) = log(z) / log(b).

Ossessione superata 😁 E tutti i link che ho trovato li butto, fatto 😁

Convertire codice Fortran in Python – 2

Continuo da qui.

Questione preliminare: perché non usare le liste?
Risposta: le liste sono per elementi eterogenei e sono lente e avide di memoria. Se i dati sono omogenei (p.es. tutti int o tutti float o casi simili) ci sono gli array mono- e pluri-dimensionali (matrici).

Python ha un modulo per la gestione degli arrays, ma c’è di meglio: NumPy.

Ogni trattazione di questo argomento inizia con un avvertimento: attenzione alla gestione degli indici, e continua con l’esempio Fortran “per colonna” invece del più comune “per riga”. OK, da verificare. C’è poi un altro aspetto cui prestare attenzione, anzi due. In Fortran il primo indice è 1; in Pthon 0; lo sanno tutti, è banale ma è facile fare confusione, esempio:

$ py3
>>> import numpy as np
>>> ar = np.arange(10)
>>> ar
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> ar.shape
(10,)
>>> ar.size
10
>>> ar[0]
0
>>> ar[5]
5
>>> ar[ar.size]
Traceback (most recent call last):
  File "", line 1, in 
IndexError: index 10 is out of bounds for axis 0 with size 10
>>> ar[ar.size - 1]
9

OK? Quando si ha una matrice attenzione ala gestione degli indici, dicono tutti. Da verificare in Fortran (ebbene sì! ma è semplice e c’è una piacevole sorpresa).

Gestione indici per per riga, file row_col.f

      program row_col

      integer row, col
      real m(1000,1000)

      do row = 1, 1000
          do col = 1, 1000
              m(row, col) = col + 10 * row
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      do row = 1, 1000
          do col = 1, 1000
              m(row, col) = m(row, col) / 2
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      end

Compilo ed eseguo:

$ gfortran -o row-col row-col.f
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,016s
user	0m0,012s
sys	0m0,004s
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,018s
user	0m0,014s
sys	0m0,004s
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,019s
user	0m0,012s
sys	0m0,008s

Uh! per un milione di dati float, real*4 in Fortran 77, il tempo di esecuzione è troppo piccolo per essere significativo. E questa è la versione lenta, ecco quella corretta, col-row.f

      program col_row

      integer row, col
      real m(1000,1000)

      do row = 1, 1000
          do col = 1, 1000
              m(col, row) = col + 10 * row
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      do row = 1, 1000
          do col = 1, 1000
              m(col, row) = m(col, row) / 2
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      end

Compilo ed eseguo:

$ gfortran -o col-row col-row.f
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,018s
user	0m0,018s
sys	0m0,000s
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,017s
user	0m0,005s
sys	0m0,013s
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,017s
user	0m0,013s
sys	0m0,004s

sì, migliore! di quanto? pochissimo!

Non siamo più ai tempi del PDP-11, nemmeno a quelli del VAX suo successore e la RAM oggi è chip e abbondante. Ecco perché il Web è pieno di foto di gatti, mica come ao tempi di Lenna!

OK, questo per il Fortran, entri Python, con Numpy.

Prima versione col-row.py

#!/usr/bin/python3

import numpy as np

M = np.arange(1000*1000, dtype=float).reshape(1000,1000)
for row in range(1000):
    for col in range(1000):
        M[col, row] = col + 1 + 10 * (row + 1)

print(M[0, 0], M[1, 199], M[999, 0])

for row in range(1000):
    for col in range(1000):
        M[col, row] = M[col, row] / 2

print(M[0, 0], M[1, 199], M[999, 0])

Eseguo:

$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,811s
user	0m0,798s
sys	0m0,012s
$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,807s
user	0m0,781s
sys	0m0,020s
$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,798s
user	0m0,776s
sys	0m0,016s

Seconda versione row-col.py

#!/usr/bin/python3

import numpy as np

M = np.arange(1000*1000, dtype=float).reshape(1000,1000)
for row in range(1000):
    for col in range(1000):
        M[row, col] = col + 1 + 10 * (row + 1)

print(M[0, 0], M[1, 199], M[999, 0])

for row in range(1000):
    for col in range(1000):
        M[row, col] = M[row, col] / 2

print(M[0, 0], M[1, 199], M[999, 0])
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,771s
user	0m0,755s
sys	0m0,016s
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,823s
user	0m0,800s
sys	0m0,024s
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,794s
user	0m0,774s
sys	0m0,020s

sì, Python è lento ma ampiamente accettabile.

OK, ho tutto quello che serve per la conversione; con Python sarà semplice aggiornare e aggiungere nuove funzionalità, i grafici, ma non solo. Per esempio capita (ahemm, sempre) di dover fare la matrice trasposta, quella con righe e colonne scambiate. A me che sono vecchio vedere come fa NumPy vengono le vertigini:

$ py3
>>> import numpy as np
>>> M = np.arange(12).reshape(3,4)
>>> M
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> M.T
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

Il manuale di NumPy è qui.

OK? vero che Python è (quasi) l’unico linguaggio in città. O almeno quello più sexy 🤩

Convertire codice Fortran in Python – 1

Si può fare ma c’è qualche aspetto da considerare.

Intanto le abitudini di una volta, complice una possibilità del Fortran non presente in Python. Nel Fortran (parlo delle versioni vecchie 77 e precedenti; il 77 è tuttora usato per quel che ne so) ci sono due tipi di sottoprogrammi: le funzioni e le subroutines. Queste ultime, affini alle funzioni C di tipo void erano di gran lunga le preferite. C’è una giustificazione: le variabili sono passate per indirizzo (reference, non so se i due termini sono rigorosamente equivalenti, questi erano quelli correnti). Esempio:

N = 8
CALL DOPPIO (N)
PRINT* N

SUBROUTINE DOPPIO (N)
N = 2 * N
END

Il risultato che ottengo è 16, cioè il valore di N modificato nella subroutine viene conservato nel main, è cambiato il valore memorizzato all’indirizzo della variabile e questo è quello che ho passato al sottoprogramma. Con Python (per quel che ne so) questo non è possibile, ecco qua:

$ py3
>>> def doppio (n):
...   n = 2 * n
...   print('in doppio:', n)
...
>>> n = 8
>>> doppio (n)
in doppio: 16
>>> print (n)
8

alla funzione doppio ho passato il valore 8 e al di fuori di questa il valore di n resta 8 indipendentemente da quanto avvenuto nel sottoprogramma. Ovvia la soluzione, la stesa da sempre, in Fortran:

N = 8
N = IDOPPIO (N)
PRINT* N

FUNTION IDOPPIO (N)
N = 2 * N
RETURN (N)
END

Notare che ho cambiato il nome alla funzione, variabili e funzioni sono di default REAL*4 tranne quelle il cui nome inizia con le lettere dell’intervallo [I..N] che sono INTEGER*2 o INTEGER*4 a seconda della versione e delle opzioni di compilazione. Caratteristico del Fortran è che la precisione viene espressa in byte e non in bit.

In Python  la funzione può ritornare una lista, torna comoda per le subroutines:

$ py3
>>> def pot(a, b, p):
...   a = a ** p
...   b = b ** p
...   return a, b
...
>>> r = pot(3, 4, 2)
>>> print(r)
(9, 16)
>>> # ovviamente
... a, b = pot(3, 4, 2)
>>> print(a)
9
>>> print(b)
16

Quindi una prima regola pratica: scrivere funzioni che ritornano i valori delle variabili che ci servono. Ma ci sono dei casi in cui questa pratica non basta, il caso di arrays e matrici di dimensioni rilevanti.

In Fortran c’è l’istruzione COMMON:

COMMON /DATI/ A, B(10), C, D
COMMON /MAT/ P(20, 20), R(10, 10, 10)

Ogni funzione o subroutine che usi A, B, C o D dovrà dichiarare il COMMON /DATI/, idem per il caso di /MAT/.

In Python si ottiene un risultato simile (meno granulare, meno raffinato ma forse meno soggetto a bug) con le variabili globali:

$ py3
>>> #a = 0
>>> b = []
>>>
>>> def popola_dati():
...   global a
...   a = 42
...   for c in range(10):
...     b.append(c ** 2)
...
>>>
>>> popola_dati()
>>> print('a =', a)
a = 42
>>> print('b =', b)
b = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

OK, notare che a devo dichiararla global nella funzione a differenza di b che dev’essere inizializzato nel main e diventa automaticamente globale. b è una lista, non esattamente l’equivalente degli arrays e matrici del Fortran. Le liste sono versatili ma onerose, da modificare, in un post successivo, pausa 😎

Copiare, a volte…

In un file Fortran del 2002 (presumibilmente) trovo questo codice:

    IF (KOND) GO TO 50
    ISTRUZIONE-1
    ISTRUZIONE-2
    ...
    ISTRUZIONE-N
50  CONTINUE

Uh! sarebbe stato scritto così prima del Fortran 77, definito nel ’78 e da me arrivato verso l’80. I vecchi continuavano a ignorarlo, era praticamente compatibile (quasi completamente) con la versione precedente e l’abitudine e il vantaggio di non dover leggere il manuale…

Per “versione precedente” non si deve intendere il Fortran IV, 1966 ma l’aggiornamento in uso. Nessun produttore usava più lo standard, vecchio e le estensioni erano parecchie, per esempio l’I/O free format, gli interi lunghi (32 bits, o à la Fortran 4 bytes) e altro ancora.

Con il 77 l’IF aveva cessato di essere monolinea e il codice avrebbe assunto questo aspetto:

    IF (.NOT.KOND) THEN
        ISTRUZIONE-1
        ISTRUZIONE-2
        ...
        ISTRUZIONE-N
    END IF

Più leggibile, e il GOTO aveva una fama che non vi dico… anzi, qui.

Ancora 2 note: in Fortran gli spazi non sono significativi, posso scrivere END IF o ENDIF, GO TO o GOTO o anche peggio, tipo G O T O. Con il 77 posso scrivere in minuscolo, anche se allora pochi lo facevano. Negli esempi non ho rispettato le colonne, non è codice che deve girare.

Questo codice a me fa pensare (con σ > 3) che sia codice copiato, vecchio di una ventina d’anni e ancora in uso dopo altri 15 😯

Nello stesso programma si trova un altro frammento notevole:

    IF (KOND.EQ.1) CALL FOO(X, Y, Z)
    IF (COND.EQ.1) GO TO 100
    IF (KOND.EQ.2) CALL BAR(X, Y, Z)
    IF (COND.EQ.2) GO TO 100
    IF (KOND.EQ.3) CALL ZAP(X, Y, Z)
    IF (COND.EQ.3) GO TO 100
100 CONTINUE

Ecco non posso credere che non sia una traduzione di uno switch:

switch (expression) {
    case item1:
        statement1;
        break;
    case item2:
        statement2;
        break;
    ...
    case itemn:
        statementn;
        break;
    case default:
        statement;
        break;
    }

D’altronde if it’s not broke, don’t fix it, anzi if it works, don’t fix it.

Un po’ di tempo fa ho trovato questo:

if cond:
    x = x        # 1
else
    istruzione-1
    istruzione-2
    istruzione-3
    x = x + y

questo funziona ma è un errore, per me. Probabilmente il programmatore pensava che il calcolo di x come in # 1 non era obbligatorio, poi strada facendo si è accorto che… Problema di analisi, se avesse fatto il disegnino con carta, matita e gomma… O almeno avesse riscritto l’if per nascondere il misfatto…

Una funzione inizia così:

def foo(x, y, z):
    if x == 0:
        return 0
    ...

È un errore? Forse no, dipende da quanto mi posso fidare che non ci siano chiamate come questa: r = foo(0, a, b). Specialmente se foo() fa parte di un modulo io la riterrei corretta.

OK, ho raccontato di codice questionabile per far vedere che non sono solo io… Anche se a me viene facile, assay 😉

Linguaggi tipizzati e non; un test

Non so come si traduce typed e –soprattutto– untyped: ho carenze linguistiche ma credo che si capisca cosa voglio dire. Inoltre il post riguarda una prova di un paio di linguaggi di scripting (ecco! di nuovo, la carenza colpisce ancora!). Tutto sommato da non leggerlo.

Una volta i linguaggi non tipizzati, essenzialmente il Basic, erano considerati di categoria inferiore. Anche se in Fortran i vecchi trovavano a ridire che scrivevi REAL*4 MEDIA o INTEGER CONTATORE trascurando la comoda regola che risparmiava tante schede (OK, esagero, mai utilizzate per lavoro). E il Basic (quasi tutte le implementazioni) suddividevano le variabili tra stringhe$ e float.

Poi sono venuti fuori il Pascal, il C e altri dove devi dichiarare il tipo (nel C quasi sempre (almeno allora, oggi non saprei)) per tutte le variabili. E non dico niente sulle funzioni in Julia che con e per via dei tipi esagerano davvero.

I linguaggi dinamici (ehi! il Basic) sono di solito non tipizzati (tutti? non lo so!). Sono meno rigorosi? Ecco, secondo me no, ma è quello che voglio verificare.

Non parlo del caso patologico di JavaScript, si sa e si evita:

$ node
> n = 5
5
> s = "5"
'5'
> t = n + s /* numero usato come stringa */
'55'
> u = n * s /* stringa usata come numero */
25

Python assegna automaticamente il tipo in questo modo

> $ py3
>>> n = 8
>>> type(n)
<class 'int'>
>>> r = 5.6
>>> type(r)
<class 'float'>
>>> s = 'ciao'
>>> type(s)
<class 'str'>

Il tipo della variabile viene ridefinito automaticamente:

>>> s = 'ciao'
>>> type(s)
<class 'str'>
>>> s = 7
>>> type(s)
<class 'int'>
>>> s = s * 1.23
>>> s
8.61
>>> type(s)
<class 'float'>
>>> s = int(s)
>>> s
8
>>> type(s)
<class 'int'>

JavaScript (Node nel mio caso) è ancora più tollerante

$ node
> s = 'ciao'
'ciao'
> typeof(s)
'string'
> s = 7
7
> typeof(s)
'number'
> s = 1.23
1.23
> typeof(s)
'number'

e come già visto cambia (casta?) automaticamente come serve (secondo lui)

> t = 'il risultato è ' + s
'il risultato è 1.23'
> typeof(t)
'string'

OK, possiamo trascurare la dichiarazione del tipo. Vediamo per quanto riguarda la precisione, chiedendo aiuto a bc, il mio tool di fiducia:

$ bc
22/7
3.14285714285714285714

Questo numero lo copio nella clipboard, per questo

$ bc
22/7
3.14285714285714285714
n1 = 3.14285714285714285714
n2 = 3.14285714285714285714 * 2
n4 = 3.14285714285714285714 * 4
n2
6.28571428571428571428
n4
12.57142857142857142856
22 - (n1 + n2 + n4)
.00000000000000000002

OK, un errore di chiusura di 2e-20 o, come direbbero i mate 2*10-20. E con scale=... potremmo esagerare ancora di più.

Cosa farebbe Python?

$ py3
>>> 22/7
3.142857142857143
>>> n1 = 3.142857142857143
>>> n2 = 3.142857142857143 * 2
>>> n4 = 3.142857142857143 * 4
>>> n2
6.285714285714286
>>> n4
12.571428571428571
>>> 22 - (n1 + n2 + n4)
0.0

OK e JavaScript?

$ node
> 22 / 7
3.142857142857143
> n1 = 3.142857142857143
3.142857142857143
> n2 = 3.142857142857143 * 2
6.285714285714286
> n4 = 3.142857142857143 * 4
12.571428571428571
> 22 - (n1 + n2 + n4)
0

Troppo semplice!” mi dicono TB e EL (h/t a entrambi, nèh!) e allora un po’ più di calcoli, il calcolo di 10! (fattoriale di 10) con i logaritmi, come si sarebbe fatto quando ero giovane:

fact10.py

#!/usr/bin/python3

from math import factorial, exp, log

lf10 = 0
for c in range(1, 10+1):
    lf10 += log(c)
f10 = exp(lf10)

print('fattoriale(10)      =', factorial(10))
print('calcolato con log() =', f10)

ed ecco:

$ py3 fact10.py
fattoriale(10)      = 3628800
calcolato con log() = 3628800.0000000084

OK 🤩 Posso continuare a usare linguaggi dinamici (la comodità della REPL) non  tipati  tipizzati. Piuttosto le difficoltà potrebbero essere altre; ma adesso pausa.

Esempi veloci di codice per il linguaggio X

Un tweet di Aaron, über-pythonista 💥 che dice che –OK, qua.

Ora io quando sento che qualcuno dice cose non tanto belle di Wikipedia non mi piace e vado a controllare, anche perché ho almeno due suggerimenti a complemento della Wiki, dovessero servire.

I linguaggi di programmazione sono tanti, troppi per conoscerli tutti. Peraltro ne servirebbero altri ancora, ce ne sono di quasi-nuovi che prossimamente (si spera) diventeranno importanti, praticamente indispensabili (nel loro campo). Intanto visto che sono tanti esaminerò solo quelli che uso, anzi nemmeno tutti quelli. In casi come questo la cosa migliore è metterli in tabella, à la Excel ma senza Excel; anzi no: CSV.

AWK, Wiki, Learn X, Rosetta

bash, Wiki, Learn X, Rosetta

C, Wiki, Learn X, Rosetta

C++, Wiki, Learn X, Rosetta

Common Lisp, Wiki, Learn X, Rosetta

Fortran, Wiki, Learn X, Rosetta

Go, Wiki, Learn X, Rosetta

Haskell, Wiki, Learn X, Rosetta

Java, Wiki, Learn X, Rosetta

JavaScript, Wiki, Learn X, Rosetta

Julia, Wiki, Learn X, Rosetta

Lua, Wiki, Learn X, Rosetta

newLISP, Wiki, Learn X, Rosetta

Octave, Wiki, Learn X, Rosetta

Python 3, Wiki, Learn X, Rosetta

Racket, Wiki, Learn X, Rosetta

Ruby, Wiki, Learn X, Rosetta

Rust, Wiki, Learn X, Rosetta

Smalltalk, Wiki, Learn X, Rosetta

Ecco. Le pagine della Wiki riportano sempre il sito del linguaggio. Octave è spesso trascurato, indicando invece la versione proprietaria (molto onerosa) MATLAB. Learn X ha spesso altre dritte. Ma –sapete che sono all’antica– questo serve solo per farsi un’idea, poi RTFM, e sarà una cosa lunga, mai dimenticare che Ankh-Morpork non è stata costruita in un giorno.

Concatenazione di programmi con pipe

Un esempio semplice, forse banale, nel caso da non leggere, ma chi viene da Windows forse queste cose non le ha ancor viste.

La filosofia di Unix, il papà di Linux, è di avere tanti tools —uh! vedi più avanti. Proprio come mi è capitato recentemente.

Non userò quel programma ma uno molto più essenziale per comodità. Nel mio caso il programma era chiuso, non era possibile modificarlo, oggi invece userò bc.

bc è versatile, ne ho già parlato recentemente ma mi ripeto. Conviene creare l’alias bc='bc -l' da mettere in ~/.bash_aliases se presente o in fondo a ~/.bashrc in modo da caricare automaticamente la libreria matematica e settare la precisione a 20 cifre decimali.

Creo lo script verprec che valuta la precisione dei calcoli di bc; ovviamente è un test senza alcun senso, serve solo per gli scopi di questo post. Ecco lo script verprec

#!/bin/bash
echo "e(l($*))" | bc -l

verifico se funziona:

$ ./verprec 1
1.00000000000000000000
$ ./verprec 2
1.99999999999999999998
$ ./verprec 0.5
.50000000000000000000
$ ./verprec 3
2.99999999999999999998
$ ./verprec 1+2
2.99999999999999999998
$ ./verprec 1 + 2
2.99999999999999999998

OK 😋 ma c’è un problema di arrotondamento, non voglio quella sfilza di 9! Vale anche per gli 0 quando non servono. Problema da risolvere.

Ma nel caso reale cui accennavo non avevo la possibilità di modificare il codice. Cosa che capita anche con bc, quando si setta la precisione a d cifre decimali con scale=d i numeri saranno tutti con d decimali, 20 in questo caso.

 Ma quando il gioco si fa duro  no scancello, senza esagerare si può rimediare in modo semplice.

Esaminiamo lo script verprec: è composto di due tools:

  • echo che fa l’eco, appunto, ripete ma sostituendo gli argomenti passati allo script con la stringa corrispondente;
  • bc che prende come input l’output di echo e valuta l’espressione; l’opzione -l non è necessaria ma metti che l’utente non abbia settato l’alias come indicato….

Il collegamento output-di-echo -> input-di-bc è dato |, il/la (non ci siamo ancora messi d’accordo sul genere) pipe, una meravigliosa idea di Douglas McIlroy.

Dice Doug [Basics of the Unix Philosophy]: This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.

Allora tutto quello che serve è un tool che arrotondi i numeri. Tra i millemila linguaggi disponibili propongo… vediamo… Python 😋

arro.py

#!/usr/bin/python3

import sys

args = sys.argv;
if len(args) > 1:
  d = int(args[1])
else:
  d=2

for line in sys.stdin:
  num = float(line)
  r = round(num, d)
  if r == int(r): r = int(r)
  print(r)

Semplice, vediamo se mi sono capito: se lo lancio con un numero lo considera come il numero di decimali (d) altrimenti utilizza d = 2. Legge poi da terminale (unità sys.stdin) il dato che gli arriva e lo trasforma in “numero con la virgola (che è un punnto)” (num). Python ha diverse funzioni predefinite di arrotondamento, nel nostro caso va bene round() ma qui si potrebbe raccontarla lunga… Infine se il risultato r è uguale al suo intero si userà quest’ultimo. In pratica:

$ echo 1.2345 | python3 arro.py 2
1.23
$ echo 1.667 | python3 arro.py 2
1.67
$ echo 1.667 | python3 arro.py 10
1.667
$ echo 1.5 | python3 arro.py 2
1.5
$ echo 2.000001 | python3 arro.py 2
2

OK 😋 Come al solito si può abilitare lo script, dopo avergli cambiato il nome togliendogli l’estensione:

$ mv arro.py arro
$ chmod +x arro

Possiamo tornare allo script iniziale, quello di bc ed ecco

$ ./verprec 1 | ./arro 2
1
$ ./verprec 2 | ./arro 2
2
$ ./verprec 0.5 | ./arro 2
0.5
$ ./verprec 1.2345 | ./arro 2
1.23
$ ./verprec 1.2345 | ./arro 3
1.234
$ ./verprec 1.2345 | ./arro 4
1.2345
$ ./verprec 1.2345 | ./arro 5
1.2345

Per chi è nuovo di Linux devo lanciare gli script prefissandoli con ./ (cioè qui, la directory in cui mi trovo) perché questa non è nella PATH; con Windows la directory corrente è sempre nella path.

Resta solo da unire i due comandi in uno solo, lasciato come esercizio 😋

Uh! ‘n altra piccola integrazione: e se il file di input non è così bello come l’input di bc?

Ecco il file txt

0
1
  2.344444
  2,345678
otto. con anche la , ecco

3  4
 misto 42

con le espressioni regolari (regex) si possono estrarre i numeri; posso anche sostituire la virgola usata al posto del punto, ecco il file extn:

extn

#!/bin/bash
sed 's/\,/\./g;s/\.[^0-9]//g;s/[^\.0-9]/\ /g;/^[\ \t]*$/d;s/^\ *//g'

lo provo

$ cat txt | ./extn
0
1
2.344444
2.345678
34
42

Va quasi bene; ma penso si possa migliorare. Eh! sì, le regexes sono magia 🤩

Anche questa da aggiungere (se necessario) allo script risultante; lasciato come esercizio 😋

L’istruzione if di bc

Il manuale dice:

if (expression) statement1 [else statement2]
The if statement evaluates the expression and executes statement1 or statement2 depending on the value of the expression. If the expression is non-zero, statement1 is executed. If statement2 is present and the value of the expression is 0, then statement2 is executed. (The else clause is an extension).

Adesso mi è chiaro ma recentemente ho fatto confusione, sbagliato. Adesso mi spiego e mi correggo.

Comincio con un (debolissimo, lo ammetto) tentativo di giustificazione. Ai miei tempi bc era più semplice, ecco il SysV Command Reference di Apollo, scaricato da qui.

A p.75 c’è

tutto OK, nella pagina successiva c’è l’ifper bc

visto? niente else. Poi non se ne parla più, l’esempio riguarda for.

Adesso non voglio dire che il mio codice (qui) l’ho scritto ricordando il manuale del 1987 o che ho consultato quello. Ho ddg-ato e mi sono fidato di una descrizione con esempio non tanto esemplare. Adesso mi spiego.

Definisco la funzione grande che ritorna 1 per un numero con 2 o più cifre. Inizio con quella incriminata, usata nel post citato:

define grande(n) {
  if (n >= 10)
    return 1
    return 0 /* bug qui */
}

/* test */
grande(5)
0
grande(20)
1

OK, funziona. Un C-ista avrebbe subito da meravigliarsi per le due alternative dell’if così scritte ma in un altro linguaggio (di cui non mi viene il nome) sarebbe OK:

(if test-expr then-expr else-expr)

Evaluates test-expr. If it produces any value other than #f, then then-expr is evaluated, and its results are the result for the if form. Otherwise, else-expr is evaluated, and its results are the result for the if form. The then-expr and else-expr are in tail position with respect to the if form.

> (if (positive? -5) (error "doesn't get here") 2)
2
> (if (positive? 5) 1 (error "doesn't get here"))
1
> (if 'we-have-no-bananas "yes" "no")

Tornando a bc e alla funzione grande che in questo caso funziona ma solo perché il return 1 conseguenza del risultato 1 (true) dell’if non esegue l’istruzione successiva. Cosa che diventa subito evidente se uso, come si dovrebbe una variabile e il return sarà il suo valore, così (versione scorretta):

/* versione scorretta */
define grande(n) {
  auto v
  if (n >= 10)
    v = 1
    v = 0
  return v
}

/* test */
grande(5)
0
grande(50)
0

Ecco, non funziona! Intanto una cosa che ho dato per scontato, anzi due: per me il comando bc viene intercettato da un alias alias bc='bc -l', cioè carica automaticamente la libreria matematica; le variabili definite con auto (in questo caso v) sono locali della funzione.

Altra cosa che ho dimenticato di dire: siccome faccio di solito parecchi errori di battitura la funzione (nei vari casi) la scrivo in un file di testo, controllo, la copio con Ctrl-C e la inserisco (pasto) nella REPL con Maiusc-Crtl-V. Questa modalità ha evidenti vantaggi ma non funziona per il debug, difatti:

/* interattivamente per il debug */
n = 20
if (n >= 10)
  v = 1
  v = 0
v
0

/* no, non va, uso la leyword else */
n = 20
if (n >= 10) v = 1 else v = 0
v
1
n = 5
if (n >= 10) v = 1 else v = 0
v
0

OK! provo a scrivere l’if in più righe come di solito

/* versione scorretta */
if (n >= 10)
  v = 1
else
(standard_in) 51: syntax error

OOPS 😯 errore! bc ha considerato terminato l’if appena l’istruzione successiva risultava terminata. Verifica

n = 5
if (n >= 10)
  v = 1 else
  v = 0
v
0

OK, else sulla stessa riga dell’assegnazione v = 1 dice a bc che l’if non è concluso e sospende l’assegnazione.

Si può migliorare la leggibilità del codice (imho) considerando che dove c’è un istruzione può esserci un blocco racchiuso da graffe, quindi:

/* versione corretta */
define grande(n) {
  auto v
  if (n >= 10) {
    v = 1
  } else {
    v = 0
  }
  return v
}

/* test */
grande(1)
0
grande(101)
1

Non mi resta che correggere il codice pubblicato nel post precedentemente indicato. E mettere l’avviso che le funzioni possono essere scritte meglio, così:

trig

pi = 4 * a(1)

define sin(x) {
  return (s(x));
}

define cos(x) {
  return c(x);
}

define tan(x) {
  auto r
  if (c(x) != 0) {
    r = (s(x) / c(x))
  } else {
    r = 1^20;
  }
  return r
}

define asin(x) {
  auto r
  if (x == 1) {
    r = (pi / 2);
  } else {
    r = a(x / sqrt(1.0 - x^2));
  }
  return r
}

define acos(x) {
  auto r
  if (x == 0) {
    r = (pi / 2);
  } else {
    r = a(sqrt(1.0 - x^2) / x);
  }
  return r
}

define atan(x) {
  return a(x)
}

e verifico:

$ bc -l trig
asin(acos(atan(tan(cos(sin(0.16))))))
.16000000000000000012

OK 💥😁 devo fare più attenzione che ho chi mi controlla, ht a loro 💥