I have an opaque type FancyDouble, which is implicitly converted from Double by using given Conversion[Double, FancyDouble] = FancyDouble(_) in the companion object scope. After that, the construction val d: FancyDouble = 0.0 works, but when I try to do the comparison like if (d == 0.0), the compiler is complaining that I can't compare FanceDouble and Double (I am expecting that it should implicitly convert the 0.0 literal into FancyDouble, like in the previous case.
How can I enable the comparison with implicit conversions?
Equality is essentially defined as (Any, Any) => Boolean. Mostly because of Java legacy. That means that in fancyDouble == double the expected type of expression double is Any and no implicit conversion will ever trigger.
However Scala 3 introduced the CanEqual typeclass to make equality a bit safer. If you want to compare values of type A and B, an implicit instance of CanEqual[A, B] needs to be in the implicit scope.
object fancy {
opaque type FancyDouble = Double
def make(d: Double): FancyDouble = d
given CanEqual[FancyDouble, Double] = CanEqual.derived
given CanEqual[Double, FancyDouble] = CanEqual.derived
}
val a = 4.2
val b = fancy.make(4.2)
assert(a == b) // ok
assert(b == a) // ok
If you enable the strictEquality flag, you even need a CanEqual[FancyDouble, FancyDouble] in order to compare two FancyDoubles.
Related
I am trying to understand Scala3 new "Multiversal Equality" feature. I am experiencing inconsistent behavior when comparing different types.
case 1. Compare Int with String:
val x = 1
val y = "One"
x == y // gives compilation error -> "Values of types Int and String cannot be compared with == or !="
Compilation error even without importing scala.language.strictEquality
This compiles in Scala2 without any errors
case 2. Compare two case classes:
case class Cat(catname: String)
case class Dog(dogname: String)
val d = Dog("Frank")
val c = Cat("Morris")
d == c // false, but it compiles
I am aware of the fact that I need to import scala.language.strictEquality to enforce Multiversal equality in case2. But why is it not required in case1?
Notice that
case 1. summon[CanEqual[Int, String]] doesn't compile even without importing scala.language.strictEquality
case 2. summon[CanEqual[Cat, Dog]]
compiles without importing scala.language.strictEquality but
doesn't compile with such importing.
a) Instances of type class CanEqual are generated by the compiler (as well as scala.reflect.ClassTag, scala.reflect.TypeTest, scala.ValueOf, scala.deriving.Mirror.Product, scala.deriving.Mirror.Sum, scala.deriving.Mirror)
val specialHandlers = List(
defn.ClassTagClass -> synthesizedClassTag,
defn.TypeTestClass -> synthesizedTypeTest,
defn.CanEqualClass -> synthesizedCanEqual,
defn.ValueOfClass -> synthesizedValueOf,
defn.Mirror_ProductClass -> synthesizedProductMirror,
defn.Mirror_SumClass -> synthesizedSumMirror,
defn.MirrorClass -> synthesizedMirror)
https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala#L489-L499
b) The thing is that the case when one of type parameters L, R of CanEqual[-L, -R] is a numeric value class (Byte, Short, Char, Int, Long, Float, Double) is handled differently:
val synthesizedCanEqual: SpecialHandler = (formal, span) =>
...
if canComparePredefined(arg1, arg2)
|| !Implicits.strictEquality && explore(validEqAnyArgs(arg1, arg2))
...
https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala#L147-L148
Notice that here if the answer is given by canComparePredefined then it doesn't matter whether strictEquality is switched on.
c) canComparePredefined calls
def canComparePredefinedClasses(cls1: ClassSymbol, cls2: ClassSymbol): Boolean =
...
if cls1.isPrimitiveValueClass then
if cls2.isPrimitiveValueClass then
cls1 == cls2 || cls1.isNumericValueClass && cls2.isNumericValueClass
else
cmpWithBoxed(cls1, cls2)
else if cls2.isPrimitiveValueClass then
cmpWithBoxed(cls2, cls1)
...
else
false
https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala#L108-L114
Notice that here if one of L, R is a numeric value class then the other must be too (taking into account boxing) so that summon[CanEqual[Int, Int]], summon[CanEqual[Double, Double]], summon[CanEqual[Int, Double]] compile but summon[CanEqual[Int, String]] doesn't compile.
Universal equality only works for types without CanEqual instances already defined. As stated in Multiversal Equality:
Even though canEqualAny is not declared as given, the compiler will still construct an canEqualAny instance as answer to an implicit search for the type CanEqual[L, R], unless L or R have CanEqual instances defined on them, or the language feature strictEquality is enabled.
and since there is already an instance of CanEqual for String defined in the companion object as:
given canEqualString: CanEqual[String, String] = derived
universal equality will not work for String nor Int (as per #DmytroMitin the compiler makes special consideration to Scala's numeric types) even with strictEquality disabled.
I'm confused about the generic type. I expect that 2.asInstanceOf[A] is cast to the type A, meanwhile, it's cast to Int.
Besides that, the input is java.lang.Long whereas the output is a list of Int (according to the definition the input and the output should be the same type). Why is that?
def whatever[A](x: A): List[A] = {
val two = 2.asInstanceOf[A]
val l = List(1.asInstanceOf[A],2.asInstanceOf[A])
println(f"Input type inside the function for 15L: ${x.getClass}")
println(f"The class of two: ${two.getClass}, the value of two: $two")
println(f"The class of the first element of l: ${l.head.getClass}, first element value: ${l.head}")
l
}
println(f"Returned from whatever function: ${whatever(15L)}")
the outupt:
Input type inside the function for 15L: class java.lang.Long
The class of two: class java.lang.Integer, the value of two: 2
The class of the first element of l: class java.lang.Integer, first element value: 1
Returned from whatever function: List(1, 2)
a.asInstanceOf[B] means:
Dear compiler;
Please forget what you think the type of a is. I know better. I know that if a isn't actually type B then my program could blow up, but I'm really very smart and that's not going to happen.
Sincerely yours, Super Programmer
In other words val b:B = a.asInstanceOf[B] won't create a new variable of type B, it will create a new variable that will be treated as if it were type B. If the actual underlying type of a is compatible with type B then everything is fine. If a's real type is incompatible with B then things blow up.
Type erasure. For the purposes of type checking 2 is cast to A; but at a later compilation stage A is erased to Object, so your code becomes equivalent to
def whatever(x: Object): List[Object] = {
val two = 2.asInstanceOf[Object]
val l = List(1.asInstanceOf[Object],2.asInstanceOf[Object])
println(f"Input type inside the function for 15L: ${x.getClass}")
println(f"The class of two: ${two.getClass}, the value of two: $two")
println(f"The class of the first element of l: ${l.head.getClass}, first element value: ${l.head}")
l
}
2.asInstanceOf[Object] is a boxing operation returning a java.lang.Integer.
If you try to actually use the return value as a List[Long] you'll eventually get a ClassCastException, e.g.
val list = whatever(15L)
val x = list(0)
x will be inferred to be Long and a cast inserted to unbox the expected java.lang.Long.
The answer from #jwvh is on point. Here I'll only add a solution in case you want to fix the problem of safely converting an Int to an A in whatever, without knowing what A is. This is of course only possible if you provide a way to build a particular A from an Int. We can do this in using a type-class:
trait BuildableFromInt[+A] {
def fromInt(i: Int): A
}
Now you only have to implicitly provide BuildableFromInt for any type A you wish to use in whatever:
object BuildableFromInt {
implicit val longFromInt: BuildableFromInt[Long] = Long.box(_)
}
and now define whatever to only accept compliant types A:
def whatever[A : BuildableFromInt](x: A): List[A] = {
val two = implicitly[BuildableFromInt[A]].fromInt(2)
// Use two like any other "A"
// ...
}
Now whatever can be used with any type for which a BuildableFromInt is available.
I am trying to define a higher order function f which accepts a variable number of parameters args of type Wrapper[T]* and a function parameter g in Scala.
The function f should decapsulate each object passed in args and then call g with the decapsulated parameters. Therefore, g has to accept exactly the same number of parameters of type T as args contains.
The closest thing I could achieve was to pass a Seq[T] to g and to use pattern matching inside of g. Like the following:
f("This", "Is", "An", "Example")(x => x match {
case Seq(a:String, b:String, c:String): //Do something.
})
With f defined like:
def f[V](args: Wrapper[T]*)
(g: (Seq[T]) => (V)) : V = {
val params = args.map(x => x.unwrap())
g(params)
}
How is it possible to accomplish a thing like this without pattern
matching?
It is possible to omit the types in the signature of g
by using type inference, but only if the number of parameters is
fixed. How could this be done in this case?
It is possible to pass
different types of parameters into varargs, if a type wildcard is
used args: Wrapper[_]*. Additionally, casting the result of
x.unwrap to AnyRef and using pattern matching in g is
necessary. This, however, completely breaks type inference and type
safety. Is there a better way to make mixing types in the varargs
possible in this special case?
I am also considering the use of scala makros to accomplish these tasks.
Did I get you right? I replaced your Wrapper with some known type, but that doesn't seem to be essential.
def f[T, V](args: T*)(g: PartialFunction[Seq[T], V]): V = g(args)
So later you can do this:
f(1,2,3) { case Seq(a,b,c) => c } // Int = 3
Okay, I've made my own Wrapper to be totally clear:
case class Wrapper[T](val x:T) {
def unwrap = x
}
def f[V](args: Wrapper[_]*)(g: PartialFunction[Seq[_], V]): V =
g(args.map(_.unwrap))
f(Wrapper("1"), Wrapper(1), Wrapper(BigInt(1))) {
case Seq(s: String, i: Int, b: BigInt) => (s, i, b)
} // res3: (String, Int, BigInt) = (1,1,1)
Regarding your concerns about type safety and conversions: as you can see, there aren't any explicit conversions in the code above, and since you are going to pattern-match with explicitly defined types, you may not to worry about these things - if some items of an undefined origin are going to show in your input, scala.MatchError will be thrown.
Is it ok, to create case classes with floating point fields, like:
case class SomeClass(a:Double, b:Double)
I guess auto generated equal method won't work in this case.
Is overriding equals the best solution?
EDIT:
if overriding equals is the way to go, I would like to avoid hardcoding epsilon ( where epsilon is defined like => |this.a-a|< epsilon). This won't compile:
case class SomeClass(a:Double, b:Double, implicit epsilon:Double)
I am looking for a way to pass epsilon without passing concert value each time
(some "implicit" magic).
I have also follow up more general question, how would you define hashcode for class with only floating point fields?
You are correct. If you are worried about precision, then you will need to override equals:
case class SomeClass(a:Double, b:Double)
SomeClass(2.2 * 3, 1.0) == SomeClass(6.6, 1.0)
// res0: Boolean = false
case class BetterClass(a: Double, b: Double) {
override def equals(obj: Any) = obj match {
case x: BetterClass =>
(this.a - x.a).abs < 0.0001 && (this.b - x.b).abs < 0.0001
case _ => false
}
}
BetterClass(2.2 * 3, 1.0) == BetterClass(6.6, 1.0)
// res1: Boolean = true
Ah, the joy of floating point numbers.
I think it is not a good idea to override equals with a fuzzy comparison. It violates all sorts of things that you usually take for granted with equality. Imagine a, b and c are some case classes with a fuzzy equals. Then it is possible to have a, b, c such that a==b, b==c but a!=c.
Then there is the behavior of hashcode to consider. If you override equals with fuzzy equality and do not override hashcode, you will not be able to use the resulting object in a hashmap or set, because a==b but a.hashcode!=b.hashcode.
The best way to solve the problem is to define an operator like =~= that provides a fuzzy comparison in addition to equals/== which (at least for immutable objects in scala) means that objects are exactly identical so that you can replace one with the other without changing the result of a calculation.
If you also want the ability to configure the precision of the comparison via an implicit, that adds another level of complexity. Here is a more complete example:
// a class to configure the comparison that will be passed to the operator
// as an implicit value
case class CompareSettings(epsilon:Double = 0.1) extends AnyVal
// add an operator =~= to double to do a fuzzy comparions
implicit class DoubleCompareExtensions(val value:Double) extends AnyVal {
def =~=(that:Double)(implicit settings:CompareSettings) : Boolean = {
// this is not a good way to do a fuzzy comparison. You should have both relative
// and absolute precision. But for an example like this it should suffice.
(value - that).abs < settings.epsilon
}
}
case class SomeClass(x:Double, y:Double) {
// we need an implicit argument of type CompareSettings
def =~=(that:SomeClass)(implicit settings:CompareSettings) =
// the implicit argument will be automatically passed on to the operators
this.x =~= that.x && this.y =~= that.y
}
// usage example
val x=1.0
val y=1.01
// this won't work since there is no implicit in scope
x =~= y
// define an implicit of the right type
implicit val compareSettings = CompareSettings(0.2)
// now this will work
x =~= y
// and this as well
SomeClass(1,2) =~= SomeClass(1.1,2)
Note that the implicit is not an argument of the class but of the operation.
I've tried this line of code
def **[A <% Numeric[A]](l:List[A],m:List[A])=l.zip(m).map({t=>t._1*t._2})
However on compilation, I get this error
error: value * is not a member of type parameter A
def **[A <% Numeric[A]](l:List[A],m:List[A])=l.zip(m).map({t=>t._1*t._2})
When I look at the source for the Numeric trait, I see a * op defined.
What am I doing wrong?
The instance of Numeric is not a number itself, but it is an object that offers operations to do the arithmetic. For example, an object num of type Numeric[Int] can add two integers like this: num.plus(3, 5) The result of this operation is the integer 7.
For integers, this is very trivial. However, for all basic numerical types, there is one implicit instance of Numeric available. And if you define your own numeric types, you can provide one.
Therefore, you should leave the bounds for A open and add an implicit parameter of type Numeric[A], with which you do the calculations. Like this:
def **[A](l:List[A],m:List[A])(implicit num:Numeric[A])=l.zip(m).map({t=>num.times(t._1, t._2)})
Of course, num.times(a,b) looks less elegant than a*b. In most of the cases, one can live with that. However, you can wrap the value a in an object of type Ops that supports operators, like this:
// given are: num:Numeric[A], a:A and b:A
val a_ops = num.mkNumericOps(a)
val product = a_ops * b
Since the method mkNumericOps is declared implicit, you can also import it and use it implicitly:
// given are: num:Numeric[A], a:A and b:A
import num._
val product = a * b
You can also solve this with a context bound. Using the context method from this answer, you can write:
def **[A : Numeric](l:List[A],m:List[A]) =
l zip m map { t => context[A]().times(t._1, t._2) }
or
def **[A : Numeric](l:List[A],m:List[A]) = {
val num = context[A]()
import num._
l zip m map { t => t._1 * t._2 }
}