[Python] Qu

Enrico Franchi enrico.franchi a gmail.com
Dom 14 Feb 2010 10:39:23 CET


On Feb 13, 2010, at 2:58 PM, Pietro Battiston wrote:


> 2) argomentare con il fatto che se un programmatore, che è poi l'utente
> del linguaggio, si trova ad utilizzare oggetti matematici - e.g. numeri
> - dovranno comportarsi, per quanto possibile, come lui si aspetta.

Sono d'accordo. Sotto ti faccio vedere come di fatto cose inaspettate
salterebbero fuori comunque.

> A volte l'1 pone dei vincoli necessari al 2; ciononostante del Python mi
> piace che pone molta attenzione al 2 (vedi passare automaticamente da
> int a long, perché di nuovo, quando come programmatore penso a "int" in
> Python penso agli interi, non agli interi modulo INT_MAX).

Come nota a margine, in C non hai sugli interi aritmetica modulare. Hai
undefined behaviour quando sfori. L'aritmetica modulare la hai sugli
unsigned (e quindi ocn UINT_MAX). Non che sia importante, solo una
precisazione.

>> sono considerate *cattiva* pratica. Proporre di abbandonare la pratica
>> considerata "buona" di
>> 
>> if []: 
>> 
>> IMHO non è particolarmente sensato.
> 
> 
> Ma infatti non l'ho proposto, ed è la terza volta che cerco di
> chiarirlo...

Su questo punto quindi siamo d'accordo. In contesti di questo tipo 0
continuerebbe a valutare a False.
Che poi, per dire, in effetti e' False che valuta a 0. Chi comanda e' 
il metodo __nonzero__, ma ok.

>> Quindi spiegami meglio, tu proponi questa semantica.
>> 
>> 0 != False -> True
>> 0 == False -> False
> 
> certo
> 
>> 
>> Ora dimmi cosa vuoi fare in questi casi:
>> 
>> 10 * False  
>> 10 + False 
> 
> il bool è convertito automaticamente in int ==> rispettivamente 0 e 10

Eh... poi ci troviamo con questo. Indichiamo con b un booleano, con n,m,...
un intero e con a qualcosa che puo' essere indifferentemente un booleano
o un intero. Il nostro obiettivo e' definire le operazioni su Bool U Int, rispettivamente
l'insieme dei booleani e degli interi. 

Normalmente ci aspettiamo che:

n + m - m == n

siamo d'accordo? Lascio perdere le parentesi, poiche' in Python, grazie al
"salto" a long abbiamo la proprieta' associativa.

Supponiamo di estendere tale cosa agli "interi o booleani". Ricorda, vogliamo
che i booleani si possano ancora usare al posto degli interi.

False + m - m != False

Infatti la prima parte viene convertita ad intera. False + m -> m 
ed m - m va a 0. Essenzialmente il problema e' che il nostro insieme
Bool U Int si trova con due elementi neutri. L'algebra ci dice che
se questi sono distinti non abbiamo piu' un gruppo, ovvero l'insieme
(Bool U Int, +) non e' un gruppo.

Questo e', in essenza, uno degli assurdi di cui parlavo. Per eliminare una
cosa sgradevole matematicamente come avere False == 0, perdiamo
alcune proprieta' fondamentali sulla struttura algebrica delle operazioni.

Nota... per non dovere definire le operazioni su Bool U Int dobbiamo
proibire l'aritmetica mista (come dicevo) oppure cambiare le proprita'
di False e True, che non dovrebbero comportarsi come elementi
cosi' particolari algebricamente come 0 ed 1.

Il problema e' che non riusciamo a trovare nessun altro buon valore.
Non possiamo metterli "in mezzo" fra due interi. Dobbiamo lasciare
loro un valore tutto particolare e distinto da quello degli interi.

Matematicamente si fa, voglio dire. A naso direi che ci imbatteremmo
in qualche altro comportamento contro-intuitivo. Un'altra possibilita'
e' intendere tutte le operazioni come operazioni su UxT dove U
e' l'insieme di tutti i valori e T quello di tutti i tipi. Ma accidenti... IMHO
viene proprio una cosa tosta.

In questo senso mi sembra che la semplicita' di False e' praticamente
un altro nome per 0 sia difficile da battere.


>> 0 < False
>> 0 > False
>> 0 < True
>> 0 > True
>> 
> 
> Per come l'ho sostenuta sinora, semplicemente l'ordering tra tipi
> diversi, ovvero int > bool in python 2.*, eccezione in python 3.* (se
> non sbaglio). Poi preferisco la prima, ma questo è un altro discorso.

Anche perche' se lanci eccezione, di fatto non e' vero che hai veramente
"aritmetica" mista. Alcune operazioni "banali" che fai con gli interi (confronto)
non puoi farle se uno dei due e' un booleano.

> Sì. Non che veda niente di proibito in
> 
> int < bool
>    definito come
> int < int(bool)
> 
> , ma non stavo pensavo a quello (che poi potrebbe essere pure più
> comodo, non so).

Sarebbe ovviamente l'unica scelta consistente con il "mischiare" interi
e booleani. Quale e' il problema?

A noi piace il principio del terzo escluso. Se due elementi non sono 
uno minore dell'altro (comunque li prendi), allora sono uguali. 

Eppure tu siccome vuoi che False "valuti" a 0 non puoi prenderlo ne
minore ne maggiore di 0, ma di fatto non hai nemmeno 0 == False.

Ovviamente, se per esempio prendi False > 0 poi ti saltano cose tipo

10 * False > 2 * False

che a rigore dovresti avere. Questo perche' False continua a comportarsi
come 0 e questo dovrebbe riflettersi nei confronti.

> Ma ti rendi conto che per nessuno di questi valori in Python si è
> ritenuto necessario introdurre un nuovo letterale (o una variabile)? Ti
> sembra veramente un caso?!

No, tutti questi valori hanno un nuovo letterale! I long li fai aggiungendo
la L in fondo, i float con il punto. Per il parser sono tutti nuovi letterali.
In Python 3 finalmente anche True e False sono letterali (o forse sono
Keywords... boh).

> 
> A me sembra un suggerimento, casomai ce ne fosse bisogno, del fatto che
> il programmatore comune considera i bool prima come valori di verità e
> solo in seguito come cose che possono "giocare" con/come i numeri.
> 
> 
>> 
>> ----
>> [0] fintanto che ci sono i long, ma possiamo estendere il concetto
>> a razionali e complessi e altri tipi numerici eventualmente definiti in seguito.
>> 
>>> Per me puoi chiamare "numero" anche una mela, se ti fa comodo, riesci a
>>> infilarlo in un certo numero di bit e riesci ad infilarci qualche
>>> operazione aritmetica.
>> 
>> No, davvero, ripeto. Di quanti bit hai bisogno per considerarlo numero? 
> 
> No, ho bisogno che sia un numero per considerarlo numero. Vorrei dire
> "nella realtà", ma ovviamente mi farei ridere dietro: diciamo nella
> mente del programmatore medio, quello per cui è stato deciso che
> esistessero False e True, nonostante la loro attuale ridondanza rispetto
> a 0 e 1.

Esatto. Sono ridondanti. Semanticamente si considera piu' chiaro scrivere
False che 0. Posso anche essere d'accordo. Poi per dire, per certe ottimizzazioni,
in Python 2.x devi usare 1.

Guarda il codice generato da 

while 1:
...

e quello da 

while True:
...


E no, non mi piace. Ma fintanto che si puo' ridefinire True... il giorno che mi
voglio fare buttare fuori a calci lo faro' scrivendo codice tipo

In [5]: i = 4

In [6]: while True:
   ...:   if i < 0: True = False
   ...:   else: i -= 1

> Non mi sembra ci sia _nessun_ altro caso, in qualsiasi tipo numerico in
> python, in cui si ha
> 
> b = a + a
> type(b) != type(a)
> a + a == b

Comunque type(bool) == type(int). Che ovviamente discende dall'essere
un bool un int. Comunque riprendo... poi stacco che mi sa che il pezzo i
interessante del messaggio lo ho gia' scritto.

Credo che la chiave sia accettare che == e' un uguaglianza *semantica*
in Python, non un'uguaglianza sintattica. In un linguaggio ad oggetti,
siamo abbastanza liberi di avere == che ci dice ok su oggetti di tipi diversi.

Io poi confesso che questa parte di programmazione ad oggetti non la amo.
Ma puo' avere un suo senso, dopotutto.

> Poi in matematica puoi dire che le ha, ma in un modo tale che, ristrette
> ai booleani, siano assolutamente stupide (e non per questo sbagliate
> formalmente). Ossia _non_ le considero operazioni sui booleani.

Non sono operazioni sui booleani. Come dicevo prima, un'operazione e'
AxA -> A, qui invece siamo 
Bool x Bool -> Int

*MA* sia A = Int U Bool, allora sono operazioni
AxA -> A

Allo stesso modo in cui in effetti le operazioni sugli interi, in Python,
sconfinano nei long. Di fatto sono definite su (IntULong^2)->IntULong


> OK, ammetto che come quoting può essere stato equivoco, ma non è un
> "trucchetto misero"... io ho tirato in ballo xor e compagnia per fare
> _notare_ che le operazioni classiche dei bool sono diverse da quelle
> degli altri numeri. 

Si, ma per un *informatico* xor e compagnia sono assolutamente operazioni
definite e definibili sugli interi, comunque. Proprio sfruttando la loro natura
binaria a basso livello.

Oggi ci si gioca meno, in univerista' non ci si lavora tantissimo. Forse le usano di
piu' ad ingegneria e magari non vengono naturali agli altri, ma di per se
sono tutte cose che ci aspettiamo da una qualunque ALU. Anzi.. conosco
ALU che hanno xor e non hanno la moltiplicazione generica. Non scherzo. :)

Roba vecchia, eh.Avevi moltiplicazione solo per due
(visto che era uno shift) e la moltiplicazione generica era una procedura.
La scrittura della quale e' lasciata per esercizio a chi voleva scriverci.

Dalla mia memoria avevo estratto che forse la CPU del game boy aveva questa
caratteristica, ma wikipedia non conferma e non smentisce, *ma* sembra piu' 
vicino a smentire.

> Mi spiego meglio: ovviamente non stavo dicendo che, data A sottoclasse
> di B, mi aspetterei
> 
> is_instance(B(), A) --> True
> 
> , ma che se A non introduce _niente_ di nuovo rispetto a B (e mi sembra
> questo il caso per i booleani nei confronti degli int - a parte la
> rappresentazione diversa, ovviamente), tanto vale abolirla...
> 
> ... a meno che non siamo tanto affezionati alla rappresentazione proprio
> perché per noi False e True _non_ sono 0 e 1.

1. se False e True fossero solo nomi per 0 e 1 sarei d'accordissimo. 
2. la differenza "semantica" la vedi per esempio su __str__ che e' ridefinito per
stampare False invece di 0
3. possibile ci siano altre cose che non mi vengono in mente, essenzialmente.

Chiaramente se in C(99|++) i bool sono motivati anche dal consumo di spazio,
questo non vale in Python, che io sappia. Spiace.



> No, ti dicevo solo che per me in questo caso Ruby è OT perché sul fatto
> che
> if [] : print "ciao"   ->   ...
> 
> è una bella cosa sono perfettamente d'accordo con te (e se ho capito
> bene - ma ci avrò scritto 10 righe in vita mia - in Ruby non è così).

Beh, Ruby propone un modello in cui interi e booleani non sono interoperabili.
Come avevo scritto 0 + false da eccezione. Era uno dei modelli proposti.
Io personalmente preferisco quello di Python, ma preferisco quello di Ruby
a quello che proponi tu (ovvero aritmetica con booleani ma False != 0).
Il perche' lo ho essenzialmente spiegato sopra.




Maggiori informazioni sulla lista Python