L'uguaglianza dovrebbe essere commutativa all'interno di una gerarchia di classi?

6

È facile definire l'operazione Equals in modi che non sono commutativi. Quando si fornisce l'uguaglianza con altri tipi, ci sono ovviamente situazioni (nella maggior parte delle lingue) in cui l'uguaglianza non essendo commutativa è inevitabile. Tuttavia, all'interno della propria gerarchia di ereditarietà in cui la classe di base radice definisce un membro di uguaglianza, un programmatore ha più controllo.

Quindi puoi creare situazioni in cui

(A = B) ≠ (B = A), where A and B both derive from base class T

Sostituendo = con la variazione appropriata per una determinata lingua. ( .Equals(_) , == , ecc.)

Questo mi sembra sbagliato, tuttavia, riconosco che potrei essere influenzato dallo sfondo in Matematica. Non sono stato in programmazione abbastanza a lungo da sapere qual è la pratica standard / accettata / preferita durante la programmazione.

La maggior parte dei programmatori accetta solo .Equals(_) potrebbe non essere commutativa e codice difendibile. Si aspettano commutatività e si infastidiscono se non lo è. In breve, quando si lavora in una gerarchia di classi, dovrei fare uno sforzo per garantire che l'uguaglianza sia commutativa?

    
posta vossad01 05.10.2012 - 19:15
fonte

4 risposte

8

Non simmetrico 1 equals rompe il contratto dell'operazione, chiaramente indicato nella documentazione (sto citando dalla documentazione di Java, ma rimane essenzialmente lo stesso in altre lingue)

The equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

Queste sono proprietà matematiche che una relazione deve avere per essere chiamata una relazione di uguaglianza. Una relazione può essere definita senza nessuna di queste proprietà 2 , ma fondamentalmente non è una relazione di uguaglianza, ed è pericoloso utilizzarla nel contesto in cui è implicita la relazione di uguaglianza.

1 Il termine giusto nel contesto dell'uguaglianza è "simmetrico", non "commutativo".

2 Ad esempio, è molto allettante definire l'uguaglianza di float s come "due numeri con la differenza assoluta minore di un piccolo epsilon"; è una pessima idea, perché rompe la transitività.

    
risposta data 05.10.2012 - 19:37
fonte
1

Non ho mai visto = definito come "è-a". = significa "è uguale a", non "è derivato da", anche nelle gerarchie di tipi e "è derivato da" è generalmente implementato con un diverso operatore o funzione. Dove l'hai visto implementato nel modo in cui descrivi? Perché è contro-intuitivo e solo strano .

La maggior parte dei programmatori non accetterebbe l'idea di "uguaglianza". Chiamerebbero "compatibilità" o "gerarchia di ereditarietà" o qualcosa di simile che descrive più correttamente cosa sta succedendo, ma l'uguaglianza dovrebbe significare "identico", non "compatibile". Quindi sì, dovrebbe essere commutativo.

    
risposta data 05.10.2012 - 19:22
fonte
0

Dipenderà dal modo in cui == è definito ...

Se viene definito solo una volta nella radice della gerarchia, mi aspetto che funzioni quando si confrontano due oggetti di tipi diversi ma derivati dalla stessa radice.

Se è (ri) definito in sottotipi, quindi mi fiderei solo su == per lavorare se gli oggetti sono dello stesso tipo esatto, non solo per essere derivati dallo stesso genitore.

Non penso di aver mai visto (A = B) ≠ (B = A) , ma se l'avessi fatto sarei probabilmente seccato.

    
risposta data 05.10.2012 - 19:37
fonte
0

Penso che la commutatività in una gerarchia di classe dovrebbe essere garantita, altrimenti ciò violerebbe il principio di sostituzione di Liskov (LSP) che dice

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

Quindi se prevedi l'uguaglianza (A == B) e inserisci un sottotipo di B, chiamato C per il quale l'uguaglianza non regge (A! = C), causeresti una violazione. Forse (C == A), ma le persone che usano le tue classi potrebbero non aspettarsi questo comportamento.

Ad esempio, voglio usare le tue classi nel mio programma:

public boolean isEqual(B x, B y) {
   return x.equals(y);
}

Dire che ho due istanze, x è di tipo B e y il sottotipo B '. Come posso o garantisco che inserisco gli oggetti nel giusto ordine nel metodo? Ciò comporterebbe molte strutture di controllo aggiuntive. E se non mi aspettassi questo comportamento, sarebbe difficile eseguire il debug.

    
risposta data 05.10.2012 - 19:32
fonte

Leggi altre domande sui tag