Perché il metodo di confronto di diversi tipi di PHP è cattivo?

3

Sto lavorando alla progettazione di un nuovo linguaggio di programmazione e sto cercando di decidere come farò confronti variabili. Insieme a molti tipi diversi di lingue, ho usato PHP per anni e personalmente ho avuto zero bug relativi alle sue operazioni di confronto diverse dalle situazioni in cui 0 = false. Nonostante questo, ho sentito molta negatività nei confronti del suo metodo di comparazione dei tipi.

Ad esempio, in PHP:

 2  <  100      # True
"2" < "100"     # True
"2" <  100      # True

In Python, il confronto delle stringhe è simile a questo:

 2  <  100      # True
"2" < "100"     # False
"2" <  100      # False

Non vedo alcun valore nell'implementazione di Python (quanto spesso hai davvero bisogno di vedere quale delle due stringhe è lessicograficamente maggiore?), e non vedo quasi alcun rischio nel metodo di PHP e un sacco di valore. So che la gente afferma che può creare errori, ma non vedo come. C'è davvero una situazione in cui si sta verificando se (100="100") e non si desidera che la stringa venga trattata come un numero? E se lo facessi davvero, potresti usare === (che ho anche sentito lamentare persone senza una ragione sostanziale).

Quindi, la mia domanda è, non contando alcune delle strane regole di conversione e confronto di PHP che riguardano gli 0 e null e stringhe miste a caratteri e numeri, ci sono dei motivi sostanziali che il confronto tra int e stringhe come questo è sbagliato, e ci sono i veri motivi per cui un operatore === è cattivo?

    
posta dallin 05.02.2014 - 02:13
fonte

6 risposte

22

Il problema più grande è che una relazione di equivalenza, il termine di math per cose come == , dovrebbe soddisfare 3 leggi.

  1. riflessività, a == a
  2. commutatività a == b significa b == a
  3. transitivity a == b e b == c significa a == c

Tutti questi sono molto intuitivi e attesi. E PHP non li segue.

'0'==0 // true
 0=='' // true
'0'==''// false, AHHHH

Quindi non è in realtà una relazione di equivalenza, che è una realizzazione piuttosto angosciante per alcune persone di mathie (me compreso).

Indica anche una delle cose che le persone odiano davvero nei cast impliciti, che spesso si comportano in modo inaspettato se combinati con il banale. Fondamentalmente è solo un insieme arbitrario di regole perché è privo di principi in questo senso, cose strane accade e tutto deve essere specificato caso per caso .

Sostanzialmente sacrificiamo la coerenza e lo sviluppatore deve assumersi l'onere aggiuntivo di assicurarsi che non ci siano conversioni divertenti (e costose) che si verificano dietro le scene. Per citare questo articolo

Language consistency is very important for developer efficiency. Every inconsistent language feature means that developers have one more thing to remember, one more reason to rely on the documentation, or one more situation that breaks their focus. A consistent language lets developers create habits and expectations that work throughout the language, learn the language much more quickly, more easily locate errors, and have fewer things to keep track of at once.

EDIT:

Un altro gioiello è incappato in

  NULL == 0
  NULL < -1

Quindi, se provi a ordinare qualcosa, non è assolutamente necessario e dipende interamente dall'ordine in cui vengono effettuati i confronti. Ad esempio supponiamo bubble sort.

  bubble_sort([NULL, -1, 0]) // [NULL, -1, 0]
  bubble_sort([0, -1, NULL]) // [-1, 0, NULL]
    
risposta data 05.02.2014 - 02:33
fonte
3

È il conflitto tra i vecchi praticanti e i teorici.

Nella teoria dell'informatica, la tipizzazione strong ecc. è considerata buona, e ci sono molte ambiguità teoriche (e alcune pratiche) che sorgono ad es. vuoi davvero ("2.000" == "2.0") per valutare true?

Tuttavia in pratica è semplicemente bello poter codificare:

if (user_choice == 2)

anziché l'equivalente Java

try {
    if (Integer.parseInt(user_choice) == 2) {
       isTwo = true;
    } else {
       isTwo = false; 
} catch (NumberFormatException nex) {
       isTwo = false;
}
    
risposta data 05.02.2014 - 02:45
fonte
3

Uno dei progetti più complicati che ho intrapreso consisteva nell'incorporare la libreria PHP nel v8 di Google per consentire a javascript di creare e accedere in altro modo direttamente agli oggetti PHP, senza una forma di bridge e senza alcuna forma di interpretazione (al di fuori di javascirpt che è). Ciò richiedeva l'accesso alle tabelle zend di un oggetto PHP e tutte le proprietà e i metodi di un oggetto PHP erano accessibili direttamente in questo modo da javascript.

Da questa esperienza ho anche avuto un assaggio della sfida di gestire i tipi di dati PHP all'interno di C (le strutture di livello inferiore) in un modo che è utile in un livello più dinamico e astratto sopra. Questo era all'interno di javascript a questo punto, non in PHP, ma era ancora il valore della vera rappresentazione interna di un oggetto PHP.

Ci sono molte sfide in queste situazioni, anche quando si tratta di confronti. Alla fine però, almeno nella mia opinione , era più importante rispettare e aderire alla natura dinamica di PHP. Era una questione di funzione, non di interpretazione logica o percezione intuitiva ecc.

La discussione sul fatto che "0" == true deve essere basata su cosa sia realmente "0". Come tipo numerico, 0 è il valore binario per falso. Tuttavia, come stringa, "0" è valido, non nullo e non vuoto. Quindi, alla fine, è una questione di pensare che "0" debba essere visto come una stringa, un tipo numerico o come terza opzione, se dovresti provare a gestirlo come entrambi in situazioni diverse. Ogni punto di vista ha valide ragioni empiriche per cui una via è più o meno utile dell'altra, ma alla fine non c'è una risposta reale o "fattuale" a questa, e sempre si discuterà di come tu, io e l'altro ragazzo valori "0".

Modifica: tieni presente che i confronti sono una delle operazioni di base di una lingua. Mentre è possibile rendere i confronti più "intelligenti", ciò richiede più logica all'interno dell'effettiva operazione di confronto stessa. Con la frequenza con cui vengono utilizzati i confronti, ciò può causare un effetto negativo sulle prestazioni, in particolare con il tipo di stringa. Quindi anche questo lato della discussione potrebbe diventare una questione di preferenza e / o priorità.

Modifica 2: per rispondere in modo più completo alla radice della domanda, i tipi dinamici necessitano di una forma di confronto dinamico o di una più ampia varietà di operatori di confronto.

  • '==' solo è sufficiente solo se l'operazione '==' è sufficientemente intelligente da sapere come gestire ciascun tipo in tutti gli scenari di utilizzo, in particolare se l'operando di sinistra e l'operando destro sono tipi diversi. Il rovescio della medaglia per l'operazione più dinamica è un probabile colpo di prestazioni. Più intelligente è l'operazione, più evidente sarà il costo.
  • più forme di operatori (come l'aggiunta di "===") possono sembrare estranee alle persone nuove nella tua lingua e probabilmente riceveranno critiche.
  • la terza opzione costringerebbe i confronti appropriati per ogni tipo in ogni momento, ma questo sacrificherà il dinamismo e andrà contro gli obiettivi di un linguaggio dinamico (almeno nella mia opinione ).

PHP tenta di gestire i confronti dinamicamente, ma include '===' per consentire anche confronti rigorosi. L'unica altra opzione sarebbe quella di rendere i confronti di default più "intelligenti" che costano le prestazioni.

Anche se è vero che gli operatori addizionali aggiungono complicazioni e confronti dinamici lasciano spazio a errori, uno studente di scuola media dovrebbe essere in grado di cogliere il concetto ( e molti lo fanno ). '==' significa che gli operandi sinistro e destro possono essere di tipi diversi, ma possono portare a risultati diversi in situazioni specifiche definite. '===' significa che sinistra e destra devono essere gli stessi tipi. Per quanto riguarda la pratica, è meglio usare '===' quando si controlla il valore di ritorno di una funzione, come pure is_string, is_int, is_bool, ecc. Questo è lo svantaggio di un linguaggio dinamico, e non farlo probabilmente porterà a risultati di confronto imprevisti, a volte.

Ma alla fine, un confronto "buono" seguirà sempre un comportamento definito e documentato. Una lingua può usare Klingon per i confronti a condizione che i risultati siano ben definiti.

    
risposta data 05.02.2014 - 23:47
fonte
1

I dati sul Web vengono trasmessi come stringhe, ad esempio una richiesta GET: www.foo.bar/?number=2

Anche se intuitivamente sappiamo che questo è un numero che viene inviato come richiesta e non come stringa, tecnicamente è una stringa poiché è l'unico tipo di dati che puoi ottenere da un URL.

IMO, i linguaggi Web come PHP e Javascript hanno una digitazione dinamica che consente la conversione automatica dei tipi a seconda del contesto principalmente a causa di ciò. Questo è un approccio pragmatico e penso che questo sia anche il motivo per cui dominano ampiamente il Web.

Penso che sia probabile che se PHP fosse stato creato per un altro ambiente rispetto al Web (come Python, C, qualunque cosa ...) le regole di battitura sarebbero state diverse e probabilmente più strongmente tipizzate e meno dinamiche, ma questo è un linguaggio Web e una tipizzazione dinamica si adatta all'ecosistema in cui si trova.

    
risposta data 06.02.2014 - 09:49
fonte
1

Un altro punto è questo. Quando ho

"x" == "x"
"2" < "100"

Mi aspetto anche che

"x2" < "x100"
"2x" < "100x"

In altre parole, il risultato della comparazione dovrebbe rimanere lo stesso quando appendo la stessa stringa sul davanti o alla fine. Questo è lo stesso con i numeri:

if a < b then so is 2*a < 2*b and 2+a < 2+b and a-2 < b-2

=== EDIT ===

Sembra che sia stato downvotato perché le persone leggono quanto sopra nel modo seguente:

String operations must behave like numerical ones.

o

String operations must behave like I said because numerical ones do.

E poi dai il "controesempio" di

2+a == a+2 therefore "2x" == "x2"??

No, non è quello che ho detto.

Qui è più formale:

Se e e § formano un Monoid su qualche tipo T, allora dovremmo averlo, per ogni x, y, z di tipo T vale quanto segue:

(z § x) < (z § y) <=> x < y
(x § z) < (y § z) <=> x < y

Da

0 and + form a Monoid on Integers
1 and * form a Monoid on Integers
"" and append form a Monoid on Strings
[] and list concatenation form a Monoid on lists, ...

potremmo aspettarci che le suddette leggi generali siano valide. Non lo fanno in PHP, ecco perché diciamo che il confronto tra stringhe PHP è rotto.

Osserva che

a § b == b § a

è non un requisito per i mono, sebbene sia vero per l'addizione e la moltiplicazione dei numeri. In particolare, non è valido concludere che l'append di stringa deve essere commutativa perché l'aggiunta di un intero è commutativa.

    
risposta data 05.02.2014 - 10:06
fonte
0

Chiedi quale sarebbe la situazione in cui vuoi confrontare 100 con "105" e vuoi che la stringa venga convertita in un numero, ma ho più problemi a venire con una situazione in cui vuoi confrontare volutamente 100 con "105".

Questa situazione accade sempre in PHP, naturalmente, così come in molti altri linguaggi di programmazione, ma convert-and-compare non è quasi mai ciò che realmente desideri . Quasi il 100% delle volte, vuoi confrontare due numeri o due stringhe e l'unico motivo per cui i tipi non corrispondono è che qualcosa non va nel modo in cui elabori l'input dell'utente.

PHP tenta di fare ciò che voglio dire, che assume come "convertire la stringa in un numero e confrontare i numeri". A prima vista, questo suona come un modo ragionevole di fare le cose, purché le ipotesi di PHP su ciò che si desidera siano corrette . Ma nel momento in cui ti allontani da questi presupposti, questo torna a morderti. Potrebbe anche non causare danni durante quella particolare operazione, invece di provocare scompiglio molto più tardi lungo la linea, ma prima può essere catturato l'errore, più è facile trovare la fonte e risolverla.

E questo, in definitiva, è il problema con il confronto dei tipi di PHP: in un tentativo ben intenzionato di fare ciò che voglio dire, maschera gli errori che si verificano in precedenza nel codice . I bug nascosti rimangono nascosti a causa di ciò, solo affiorando in seguito per causare problemi.

    
risposta data 06.02.2014 - 20:41
fonte