2015-05-20 6 views
5

Ich experimentiere mit pfadabhängigen Typen und stoße auf ein Problem, wenn ich versuche, eine scalaz.Equal Instanz dafür zu schreiben. Ich habe die folgende Struktur:scalaz.Equal für pfadabhängige Typen

class A { 
    class B 
} 

val a1 = new A 
val b1 = new a1.B // type a1.B 

val a2 = new A 
val b2 = new a2.B //type a2.B 

ich zum ersten Mal b1 „unequalable“ machen wollte (ist das ein Wort?) Zu b2 bei der Kompilierung, die ich mit folgenden erreicht:

import scalaz._ 
import Scalaz._ 

implicit def BEqual[X <: A#B]: scalaz.Equal[X] = Equal.equalA 

b1 === b1 //true 
b1 === b2 // doesn't compile, good 
b2 === b1 // doesn't compile, good 

Meinem zweiter Versuch war versuchen, Gleichheit weniger restriktiv, so dass Instanzen von A#B zu machen miteinander verglichen werden, aber nicht auf andere Arten, mit:

implicit val BEqual: scalaz.Equal[A#B] = Equal.equalA 

Aber es wie erwartet funktioniert nicht:

b1 === b2 //doesnt' compile, === is not a member of a1.B 

Dies funktioniert jedoch:

BEqual.equal(b1,b2) //compiles 
BEqual.equal(b1,"string") //doesnt' compile, good 

So würde ich gerne wissen, warum die === nicht funktioniert und wenn ich eine Instanz schreiben von Equal das würde für alle A#B s gelten?

Ich versuchte eine Hausbraulösung mit impliziter Konvertierung und es funktionierte.

implicit class abEqual(ab: A#B) { 
    def eqab(ab2: A#B) = ab == ab2 
} 

b1.eqab(b2) //ok 
b2.eqab(b1) //ok 
b1.eqab("String") //doesn't compile, good 

Warum also nicht diese Arbeit mit scalaz.Equal?

Antwort

4

In Ihrem ersten BEqual Sie sagen, dass Sie für jeden Subtyp von A#B eine Equal Instanz für diesen Subtyp zur Verfügung stellen möchten. Wenn der Compiler b1 === sieht, findet er daher die Equal[a.B] Instanz, da der statische Typ b1a.B ist. Das lässt die Dinge so laufen, wie Sie es erwarten. In Ihrer zweiten BEqual definieren Sie eine Equal Instanz nur für A#B. Das bedeutet, dass auch b1 === b1 nicht kompiliert wird, da der statische Typ b1 spezifischer als A#B ist und Equal in seinem Typparameter invariant ist. Wenn Sie Ihre Werte upCast wird die Instanz gut funktionieren:

scala> val ab1: A#B = b1 
ab1: A#B = [email protected] 

scala> val ab2: A#B = b2 
ab2: A#B = [email protected] 

scala> ab1 === ab2 
res1: Boolean = false 

In der Version, wo Sie BEqual.equal direkt anrufen, sind Sie im Wesentlichen erreichen die gleiche Sache-Methodenargumente immer covariant sind, also wenn Sie etwas statisch typisierte passieren als a.B als A#B Argument wird alles gut funktionieren. In Ihrer handrollierten impliziten Klasse sagen Sie auch nur, dass Sie mit einem alten A#B arbeiten möchten.

Sie können die gleiche Art der Sache sehen, wenn Sie Some(1) === Some(1) gegen Option(1) === Option(1) (oder some(1) === some(1)) schreiben. Scalaz bietet eine Equal für Option[A: Equal], aber nicht für Some[A: Equal], und wenn das erste Argument einen spezifischeren statischen Typ hat, wird die Option Instanz nicht gefunden.

Dies ist nicht etwas, das Sie arbeiten möchten, da die Invarianz von Scalaz Equal beabsichtigt ist. Wenn Sie in diesem Kontext mit A#B Werten als A#B Werte arbeiten möchten, müssen Sie sie explizit aktualisieren.

+1

"und" Equal "ist invariant in seinem Typparameter". Genau da war das Detail, das ich übersehen habe! Ich stimme zu, dass ich nicht daran arbeiten möchte, es ist das richtige Verhalten! Vielen Dank! – Chirlo