[Python] 0 in (False,) // 0 == False

Pietro Battiston toobaz a email.it
Gio 11 Feb 2010 00:24:54 CET


Il giorno mer, 10/02/2010 alle 22.48 +0100, Enrico Franchi ha scritto:
> On Feb 6, 2010, at 11:46 PM, Pietro Battiston wrote:
> 
> > Il punto che forse non è chiaro è cosa sia l'aritmetica.
> 
> Sarebbe da capire a chi non è chiaro cosa sia. Sono abbastanza sicuro
> di non essere io quello con dubbi o lacune. Qui si sta parlando di aritmetica
> mista fra interi e booleani. Che a te da fastidio, a me e a Guido no.
> 
> Quello che io ho scritto è semplicemente che *se* vuoi aritmetica che 
> coinvolga anche i booleani
> di fatto la cosa più naturale è che False sia equivalente a 0 e True ad 1,
> al di la del fatto che bool is-a int per l'implementazione corrente.
> 
> Se poi bandisci tale aritmetica hai semplicemente un linguaggio
> decisamente meno comodo senza essenzialmente nessun vantaggio
> concreto.
> 

Quando si parla di vantaggi concreti, preferisco farlo davanti a dati
concreti.

> 
> > Sia dal punto di vista matematico che da quello del Python, "==" è
> > qualcosa che non ha _niente_ a che fare con l'aritmetica: dal punto di
> > vista logico-matematico perché l'eguaglianza non è (necessariamente) un
> > operatore, dal punto di vista di come ragiona il Python perché "==" può
> > confrontare due oggetti qualsiasi, non solo numerici. Diventa
> > l'uguaglianza tra numeri... esattamente quando gli argomenti sono due
> > numeri.
> 
> 1. Da un punto di vista matematico il predicato = (che da pythonisti chiamiamo ==)
> ha *tantissimo* a che fare con l'aritmetica stessa. Non so se hai fatto un minimo
> di fondamenti, ma fra gli assiomi di peano ci sono precisamente quelli che
> riguardano =, senza non fondi l'aritmetica sulla set theory. Puoi fondarla sulla
> logica combinatoria, ma ovviamente stiamo spostandoci alquanto.

Uhm, fammici pensare... sì, ho fatto un minimo di fondamenti.

Ovviamente non intendevo che l'aritmetica possa esistere senza
uguaglianza, ma che il simbolo "=" può essere definito ben a monte
dell'aritmetica, in maniera vagamente simmetrica a quel che succede
appunto nel Python.

> 
> 2. Dal punto di vista "matematico" un operatore come il == è una bestiaccia. 
> Immagino tu non abbia lavorato più di tanto (suppongo nulla) sulla semantica
> formale dei linguaggi di programmazione.

Se le tue intuizioni informatiche sono azzeccate quanto quelle
personali, non mi stupisce la confusione che fai parlandone.

(N.B: non risponderò in maniera polemica a parti della tua email che non
siano gratuitamente polemiche. Anzi, facciamo che alle altre parti
gratuitamente polemiche non rispondo proprio.)

> Formalizzare un linguaggio ad oggetti
> è abbastanza un bagno di sangue (fidati, è un bagno di sangue anche avere
> un linguaggetto come C). Formalizzare qualcosa come Python è essenzialmente
> un suicidio. La semantica denotazionale è essenzialmente inadatta. Io avevo
> lavorato parecchio con structured operational semantics, che funzionerebbe
> anche decentemente... ma il dinamismo di Python sarebbe una iattura.
> 
>  Quindi, tiriamo fuori la matematica dal discorso. O le cose si fanno bene, e
> ti garantisco che quando si va sul formale divento *davvero* sgradevolmente
> pignolo, oppure la tiriamo indietro. Ma fino in fondo.

La tua teoria secondo cui tutto deve necessariamente essere così com'è
si poteva (almeno, mi ero figurato che si potesse) motivare dal punto di
vista dell'eleganza matematica _o_ dell'attinenza alla mentalità
pythonesca: se tu dici che la matematica non c'entra niente, tirala pure
via, altrimenti vai fino in fondo e facciamo le cose "bene", la
precisione non è mai sgradevole.


> Quindi rimaniamo al problema fondamentale: stiamo cercando di avere
> booleani che si comportano come gli interi. Se no, devi proibire le commistioni.
> Ed essenzialmente hai un altro linguaggio, che non è Python, che è più scomodo
> di Python e che, per coerenza, deve andare contro molte convenzioni ed idiomi
> pythonici. 
> 
> Già... se ci troviamo 
> 
> if 0: print "ciao" -> ciao
> 
> mi aspetto anche 
> 
> if []: print "ciao" -> ciao
> 
> e per risolvere la cosa... if bool([])? Fa schifo. Allora tanto vale 
> prendersi
> 
> if len([]) == 0:
> 
> 
> che è un tipico idioma anti-pythonico, sconsigliato a tutti i niubbi.
> 

Stai scherzando?! Questo punto è stato chiarito 6 mail fa.
_Evidentemente_
if 0: print "ciao" -> ...
if []: print "ciao" -> ...

> > Stai scherzando?! Questo punto è stato chiarito 5 mail fa.
> > _Evidentemente_ bool(0) == False.
> 
> Nope. Tu parti dal presupposto che i bool e gli int debbano essere tipi distinti.
> E guarda caso concludi che debbano essere tipi distinti.

Veramente ho concluso che _potrebbero_ benissimo esserlo, e che io lo
troverei più intuitivo - a prescindere dall'abitudine, ovviamente.

> 
> Il che porta un po' a quello che veramente è stato detto alcune mail fa.
> Ovvero che di per se puoi benissimo tirare un'eccezione ogni volta che
> mischi interi e booleani. 

Non ne vedo affatto la ragione. Se dopo tutte queste mail ti sfugge
ancora la semantica che ho in mente, te la rispiego: il comportamento
dei bool rimane _ovunque_ quello che è, tranne che 0 != False (e
ovviamente ottieni risultati diversi se vai a vedere l'ereditarietà).

Come si ottiene? Levando l'ereditarietà e ridefinendo gli operatori
necessari. Se c'è qualcosa di naif in questa idea di implementazione,
sono tutt'orecchi (occhi), ma se la semantica di per sé non fosse valida
forse gli "assurdi completi" che menzionavi varie mail fa sarebbero
ormai venuti alla luce.

> 
> Ma *non* è quello che vuole Guido. Per inciso continuo a non vedere 
> il vantaggio di farlo in Python. E a quel punto mi aspetto anche che l'inferno
> si scateni quando mischio float e interi. 

Immagina pure, io più chiaro non potevo essere.

> 
> >> Comunque davvero, la cosa più naturale è vedere True e False come numeri a 1-bit.
> > 
> > Non dubito che per te possa essere (ormai?) naturale...
> 
> Ormai? Non so. Non vedo perchè un numero di un bit non debba essere un numero.
> Ah, tu dici che non è un numero. Ok. Un numero di due bit è un numero? No?
> Uno di 31? No? uno di 32? Si. Ah, ok.
> 

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. Continuerò solo a suggerire che il fatto che tu
riesca a memorizzarla in un tot di bit ed a farla giocare in modo carino
con gli altri numeri non obbliga me a chiamarla "numero".

In realtà ovviamente non è un problema di nomi. Il fatto è che non dirò
"guarda, miracolo dell'informatica, la mela è un numero", ma "guarda,
miracolo del duck typing, la mela si comporta come un numero"!
E mi arrabbierò se scoprirò che
mela == 0
, ma non perché ci sia niente di "assurdo completo", solo perché quando
penso ad una mela non penso ad un numero, e non solo vorrei (in modo del
tutto soggettivo) che il "==" rispecchiasse questa mia visione, ma è un
_fatto_ che la mia visione è più _ricca_, perché avrei
    mela != 0
ma potrei fare
    assert( int(mela) == 0 )
tutte le volte che voglio.


> >> Non ci formalizziamo perchè 0 == 0L, ma ci da fastidio False. 
> > 
> > ... ma renditi conto che questo è un argomento che non c'entra niente.
> 
> Come non centra niente? 0L è lo 0 dei long, 0 degli int, False dei bool.
> Caso che mai non ti fosse sfuggito, long e int sono tipi *diversi* in Python.
> Eppure non ti stupisci che lo 0 dei long sia diverso dallo 0 degli interi.
> Per non parlare dello 0 dei float e quello degli interi.
> 

Sì, python tratta i numeri come numeri.

> 
> > Tutti i tipi numerici hanno somma, sottrazione, moltiplicazione,
> > divisione (questa, con qualche distinguo), che definiscono su di essi
> > delle immersioni che sono più che canoniche, sono _ovvie_: i naturali
> > (chiusi solo per la somma) sono degli interi (chiusi per somma,
> > sottrazione, prodotto), che sono dei razionali (OK, questi l'informatica
> > per quanto ne so ce li risparmia), che sono dei reali (chiusi per tutte
> > le operazioni - tralasciando la rappresentazione approssimata), che
> > volendo sono dei complessi.
> 
> Ti mancano un sacco di pezzi, mi dispiace. Di fatto ti aspetti erroneamente
> che i tipi numerici in macchina si comportino come quelli della matematica.
> 
> In primo luogo "l'informatica" non ci risparmia affatto i razionali. In scheme 
> per esempio sono abbastanza standard (anzi, sarebbero nello standard).
> Python stesso li implementa in una libreria standard, sebbene da poco.
> 
> Il tuo discorso purtroppo cade in quanto tu hai l'erronea convinzione che 
> quello che hai studiato in algebra elementare debba valere su un computer.
> 
> I reali non ci sono. E non ci possono essere. La dimostrazione è pure banale,
> quindi dimenticali. Quella che tu definisci "approssimazione" ha un piccolo
> problema: le operazioni che ci sono definite sopra *non* hanno le proprietà
> delle operazioni che hai sui reali. Tipicamente non hai nemmeno la proprietà
> associativa. Non sei manco in un semigruppo. 
> 
> Idem per gli interi. Gli interi (di C) *non* hanno proprietà associativa sull'addizione.
> Sono "fatti meglio" gli unsigned che hanno una buona aritmetica modulare. Ma
> finisce li la storia.
> 
> In Python invece la somma non è nemmeno un operazione? Come come?
> Se sommi due interi, puoi ottenere un long. Ergo quella, matematicamente,
> non è un'operazione. Certo, è comodo. Ma appunto, abbandona quello che
> "dovrebbe essere".
> 

Stai cercando di motivarmi con dettagli (non nel senso di "trascurabili
per l'implementatore", ma nel senso di "auspicabilmente trascurabili per
l'utilizzatore") la "comodità" e la "sensatezza" (e addirittura la
necessarietà) di alcune regole degli operatori? In tutta onestà, non è
quello di cui stavamo parlando.
E sì, ci sono decine di librerie che ti daranno i razionali: non li ho
citati anche perché almeno nei casi che conosco io (es. GMP, in Python
non ne ho mai fatto uso) i razionali sono l'unico (tra quelli citati)
tipo numerico che non approssima un tipo che hai in mente, ma ti
fornisce esattamente quel che cerchi, e quindi ha meno senso considerare
l'aspetto "intuitivo" della traduzione del concetto matematico
nell'informatica.

Se ti scoccia che ho usato la parola "reale" per alludere ai float, mi
scuso per la bestemmia, e mi auguro che tu abbia capito che _se_ in
_Python_ io ho il dubbio che i float che sto utilizzando si comportino
in modo sensibilmente (quale sia il mio livello di sensibilità, sono
affari miei) diverso dai reali che ho in mente, _difficilmente_
risolverò il problema ragionando direttamente in termini di float -
solitamente dovrò cambiare algoritmi, o affidarmi direttamente a
librerie/tipi diversi che riusciranno meglio ad approssimare quel che ho
in mente.

Non sto "dimenticando" l'aspetto dell'implementazione, sto solo dicendo
che a meno di vincoli particolari non è certamente quell'aspetto che
decide cosa sia "comodo" e/o "assurdo" in un linguaggio di
programmazione ad alto livello.

L'esempio che fai sotto con int e long è infatti illuminante in tal
senso. Il pythonista medio può felicemente fregarsene di quanto sia
l'int massimo.

> 
> > In tutto ciò i booleani, che sono nati come logica a due valori in cui
> > le due operazioni "standard" erano or (l'equivalente intero è "max", non
> > una delle operazioni aritmetiche) e and ("min"), sono un caso a parte.
> > Ma non c'è solo problema storico: c'è che seppure l'addizione (così come
> > la sottrazione) assomiglia ad un'operazione logica, lo xor, purtroppo
> > l'insieme dei booleani _non è chiuso_ rispetto ad essa (dato che anche
> > assumendo che i booleani siano numeri, True + True non sarebbe un
> > booleano): ovvero non si comporta affatto come lo xor. Quindi _comunque_
> > quando parliamo di addizione di booleani è piuttosto ipocrita spacciarla
> > per un'operazione "nativa": è, come tutti hanno ammesso, una comodissima
> > eredità del C.
> 
> Non è un'operazione infatti. Esattamente come in Python non è un'operazione
> la somma. Come come?
> 
> Dal momento che int è un tipo *limitato* quando fai somma di due interi, puoi sforare
> e quando succede:
> 1. dai eccezione
> 2. cambi tipo
> 
> Il comportamento in Python è il secondo. Secondo le definizioni dell'aritmetica
> e dell'algebra elementare che io non dovrei sapere (rotfl) questa *non* è un'operazione.
> 
> Un'operazione in senso matematico è la somma sugli unsigned (che infatti hanno
> esattamente il comportamento dell'aritmetica modulare). Non è un'operazione una
> cosa che ti tira un integer overflow e non è un'operazione una 
> 
> AxA -> B dove B non e' incluso in A.

Fin qui si tratta di dettagli di implementazione, vedi sopra.

> 
> Infatti ti sei ben guardato da definire la sottrazione un'operazione sui naturali e la divisione
> un'operazione sugli interi. 

Questo è tutt'altro che un dettaglio di implementazione, e infatti... mi
sono ben guardato, appunto.

> 
> Ecco... la somma nemmeno lei è un'operazione. Questo se non vogliamo
> considerare l'estensione automatica del tipo.
> 
> Apparentemente sommare due interi e ottenere automaticamente un long non ti
> infastidisce, sommare due booleani e ottenere un int si. Boh.

A dire la verità no.

> 
> BTW, non tirare in ballo xor e compagnia, al momento non hanno nulla a che vedere.
> 

Nulla a che vedere con i booleani? La somma invece sì, eh?

> Una volta ammessa sta somma fra interi che da un long, per quando mi riguarda ammetto
> anche la somma su booleani che mi da un int.

Infatti io la ammetto semplicemente perché è comoda e non ha
controindicazioni, ma non nego che anche l'abitudine possa avere il suo
peso.

> 
> Per inciso, le operazioni booleane sono facilmente estensibili agli interi
> come operazioni bit a bit. Quindi continuo a non vedere il problema.
> 

Di nuovo, ereditando dal C ci manca solo che ne perdiamo le comodità...

> 
> > D'altronde, vedila in un altro modo: se per te è perfettamente naturale
> > che i booleani siano semplicemente (un piccolo sottoinsieme degli)
> > interi, e ti sembra quindi naturale che siano _identici_ in tutto e per
> > tutto rispettivamente a 0 e 1, non ti sembrerebbe naturale anche che
> > 
> > In [1]: isinstance(1,bool)
> > Out[1]: True
> > 
> > ?!
> 
> Solo se non avessi capito una pura fava di niente di progettazione ad oggetti
> e avessi invertito il vincolo di ereditarietà nella mia testa.
> 
> Poi essere "identici" è una proprietà forte. In genere non mi interessa.
> Quello che mi interessa (e che è alla base della programmazione con 
> linguaggi dinamici) è *si comporta come*.

Appunto, ma se ogni volta che due oggetti *si comportano come* nel senso
del duck typing avessi che risultano ==, impazzirei.

> 
> Oltretutto, quello che probabilmente ti sfugge è che se un animale è un cane,
> un cane non è un intero.

Questo mi sfugge, sì. Forse manca la conclusione "ogni intero è
mortale"?

> Ergo se un booleano è un intero, un interno non è
> un booleano.

Ah, beh, che sia così nell'implementazione attuale non c'è dubbio,
grazie.

> 
> E, in conclusione, quella dell'ereditarietà è solo una scorciatoia implementativa.
> Quello che Guido vuole e che i Pythonisti si aspettano è che i booleani si
> possano usare dove si possono usare gli interi. Punto e stop. Sarebbe meglio
> che un booleano non fosse un intero ma si comportasse come tale. Amen.

Se tutto fosse come ora, l'implementazione diversa per me sarebbe
semplicemente irrilevante.

> 
> > La scomoda verità è che se volessimo una cosa "pulita" con False != 0,
> > il modo giusto sarebbe che, come in C, False e True fossero
> > semplicemente _definiti_ individualmente come 0 e 1. Solo che il tipo
> > "bool" ci fa comodo... mentre la riga di codice che ho scritto sopra è
> > assolutamente inutile.
> 
> No, la riga di sopra è utilissima a mostrare la confusione che hai in testa
> e che spero di riuscire a sciogliere nei prossimi post. 
> 
> Nella tua crociata contro if 0 non ti sei accorto di una cosa ben più drammatica.

Ehm... veramente...

> True e False, in Python, non sono letterali. Sono *variabili*. Come come?
> 
> >>> True = False
> >>> if True: print "buh!"
> ... 
> >>> 
> 
> Questo si che fa un po' cacare. Certo un pervertito che va a rinominare True
> si merita tutto il male che può capitargli.

... la prima volta che ci sono incappato ci ho dato un po' giù di matto.
Non escluderei che ce ne sia traccia da qualche parte negli archivi
della mailing list.

> > Ciò detto, ho già scritto che in fondo me ne frega assai: è evidente
> > che, posto che Guido si è già espresso, tutto il thread è puramente
> > teorico.
> 
> Considera anche questo... in Ruby false e nil sono gli unici valori veramente
> falsi. Ovviamente Ruby funziona e tutto... ma viene spesso elencato fra le cose
> contro-intuitive di Ruby. 

Di nuovo, trovo il duck typing fantastico.
Di nuovo.
Di nuovo?


ciao

Pietro



Maggiori informazioni sulla lista Python