-
Notifications
You must be signed in to change notification settings - Fork 72
Support for Multiversal Equality #100
Comments
That appears to be intended, because an instance of |
I've done a few experiments about how to type methods like The results might be affected by scala/scala3#2707. EDIT: what's below are a few alternative experiments. I'm not sure how to summarize them because I'm not sure what's the best course of action 😢 //import dotty.DottyPredef.{eqAny => _, _} // makes a difference
//import scala.{Eq => _, _} //makes no difference.
object Main {
def equal[S, T](s: S, t: T) = s == t // it seems this should fail without eqAny.
def contravWitness[A, B >: A](e : Eq[A, B]): Eq[A, A] = e
//def contravWitness[A, B >: A](e : Eq[A, B]): Eq[B, B] = e
def contains[A,B >: A](ls: List[A], b: B)(implicit eq: Eq[A,B]) = ls.exists(_ == b)
}
class Cell1[+T : [T] => Eq[T, T]](val value: T) {
def contains[U >: T : [U] => Eq[U, T]](that: U) = value == that
}
class Cell2[+T](val value: T)(implicit _e: Eq[T, T]) {
def contains[U](that: U)(implicit eq: Eq[T, U]): Boolean =
value == that
}
class Cell3[+T](val value: T)(implicit _e: Eq[T, T]) {
def contains[U >: T](that: U) = value == that
def append1[U >: T](u: U): Cell3[U] =
new Cell3(u) //uses eqAny
def append2[U >: T](u: U)(implicit eq: Eq[T, U]): Cell3[U] =
new Cell3(u) //uses eqAny
}
class ListCell[+T](val values: List[T])(implicit _e: Eq[T, T]) {
def contains0[U >: T](that: U): Boolean =
values.map(new Cell3[T](_)).exists(_.contains(that))
def contains[U](that: U)(implicit eq: Eq[T, U]): Boolean =
values.map(new Cell2[T](_)).exists(_.contains(that))
def append1[U >: T](u: U): ListCell[U] =
new ListCell(values :+ u) //uses eqAny
def append2[U >: T](u: U)(implicit eq: Eq[T, U]): ListCell[U] =
new ListCell(values :+ u) //uses eqAny
def append3[U >: T](u: U)(implicit eq: Eq[U, U]): ListCell[U] =
new ListCell(values :+ u)
} |
I don't understand why you want to put an implicit on the As for classes such as |
@LPTK thanks for the comments. CellN classes are just a minimal model of what you need for collections using equality like I agree that having implicit arguments for When writing these classes, do you hide
|
We can look at it this way:
I think EDIT: Just pressed the "comment and close" button by mistake; subsequently reopened the issue... |
Ok, that makes some sense, though supporting it stays hard. But can you now use contains on your Set of 1 and "ok", if it asks for an Eq instance? Thinking a bit about it, it seems that's only possible using eqAny, and I think this use would be valid. But this probably needs more investigation. |
I think there's a much simpler solution to the issue with class ListCell[+T](val values: List[T]) {
def contains[U >: T](that: U) = values.contains(that)
}
object Test {
new ListCell(List(1,2,3)).contains("foo") // compiles because we can set U=Any
} Everything would be fine if we wrote import scala.annotation.unchecked.uncheckedVariance
class ListCell[+T](val values: List[T]) {
// This use of uncheckedVariance is safe because we won't store `that` anywhere
def contains(that: T @uncheckedVariance) = values.contains(that)
}
object Test {
new ListCell(List(1,2,3)).contains("foo") // does not compile, as expected
new ListCell(List(1,2,3)).contains(42) // but this works
} If using |
What about searching Numbers among other Numbers? You know, numbers of
different type? Or proxies? Repeat for any Eq instance across types.
If you go this way, do you show or hide uncheckedVariance in the docs?
…On Jun 8, 2017 14:35, "Guillaume Martres" ***@***.***> wrote:
I think there's a much simpler solution to the issue with contains that
does not involve Eq. The core problem is that the signature of contains
is not what we want:
class ListCell[+T](val values: List[T]) {
def contains[U >: T](that: U) = values.contains(that)
}
object Test {
new ListCell(List(1,2,3)).contains("foo") // compiles because we can set U=Any
}
Everything would be fine if we wrote contains(that: T) but that's not
possible because of variance checks. However, here it's perfectly fine to
ignore variance, it's just that the compiler is not smart enough to see
that, but we can force its hand:
import scala.annotation.unchecked.uncheckedVariance
class ListCell[+T](val values: List[T]) {
// This use of uncheckedVariance is safe because we won't store `that` anywhere
def contains(that: T @uncheckedVariance) = values.contains(that)
}
object Test {
new ListCell(List(1,2,3)).contains("foo") // does not compile, as expected
new ListCell(List(1,2,3)).contains(42) // but this works
}
If using @uncheckedVariance shocks you consider that the collection
strawman already uses it in several places: https://github.com/scala/
collection-strawman/search?utf8=%E2%9C%93&q=uncheckedvariance&type= :)
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#100 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AARsqK4qRWk6bumMEUXO7n3HI5MIavSFks5sB-qJgaJpZM4NxOKr>
.
|
Just use
You need to be honest and show them, or fix the compiler so that they're not needed ;) |
What's the problem with
I think the compiler works exactly as intended here. By having So it should not be possible to allow breaches such as the one you suggest because the implementation of Here is your example slightly expanded: import scala.annotation.unchecked.uncheckedVariance
// Your proposal:
class ListCell[+T](val values: List[T]) {
def contains(that: T @uncheckedVariance) = values.contains(that)
}
// Now consider this:
def anyListCell: ListCell[Any] = new ListCell[Int](Nil) {
override def contains(that: Int) = that + 1 > 0
}
anyListCell.contains("oops")
|
Good point, you'd also need to make |
With
It's not clear if the compiler can be soundly fixed (nobody proved that a final I claim that a signature with
What I meant: we should be consistent in these situations. When you write:
the overall result is "let's use one complicated solution for
Well, on HashSet that means "just use a O(N) algorithm rather than a O(1) algorithm". Convert your |
My impression from the discussion here (and around multiversal equality in Dotty) so far is that it's too soon to incorporate this into the collection design. I suggest to explore this further in Dotty in a fork or once we're done with the integration into Scala 2.13. Adding |
I agree more research seems indeed needed. Should we move these questions to the Dotty repo? And what's the process for these language changes? I also wonder how such language changes are going to be decided upon. Will multiversal equality be subject to the SIP before it's in the stable language? |
While we're here: I just realized that @smarter proposal arguably exploits a typechecker misbehavior in Scala and Dotty, violating in some sense the Liskov Substitution Principle: everything possible on values of Here's an example, that gives the same behavior in Scala and Dotty's REPLs (including workarounds for scala/scala3#2789):
That means that operations are possible in |
Note that this is not the only occurrence of the violation. I can write |
I agree with @szeiger comment. Therefore I’m closing this issue. |
With multiversal equality in Dotty (scala/scala3#1247), it would seem inconsistent not to use it for operations based on
==
such ascontains
andindexOf
, which are currently pretty unsafe:It has been agreed on before that these methods could benefit from M-E. Indeed, one can write things like:
In order to make that work with current Scala, we could have a single
Eq
implicit available there so thatcontains
would always work (as before).One thing I noticed that may or may not be a problem is that adding a subtyping constraint to one of the type parameters makes the code type check again...
The text was updated successfully, but these errors were encountered: