I have a question concerning Scala case classes. I have a case class that is a composition of other types as below:
case class MyCaseClass(typeA: TypeA, typeB: TypeB) {
override def equals(obj: scala.Any): Boolean = super.equals(obj) // generated by intelliJ
}
Both my TypeA and TypeB are normal Scala classes and what I want is that, two instances of MyCaseClass is equal only if all the properties of typeA and typeB are equal. I could of course do this check in the equals method directly in my case class like below:
case class MyCaseClass(typeA: TypeA, typeB: TypeB) {
....
def equals(obj: Any): Boolean = obj match {
case myCase: MyCaseClass => myCase.typeA.equals(myCase.typeB)
case _ => false
}
}
With respect to the example snippet above, I have the following questions:
If my TypeA is a case class and my TypeB is a normal Scala class, would this means that I have to implement equals method just in TypeB where in my TypeA being a case class would get the equals method by default and I do not have to provide one?
My MyCaseClass would be used in such a way that I have a sequence of MyCaseClass and a primitive value (Double or Integer) as a tuple Seq(MyCaseClass, Double). Now if I do a groupBy operation on MyCaseClass from the Sequence, will they be groupedBy based on the equals method?
You're inheriting nothing, so the super.equals you're getting, possibly that of Product2 (assuming it has a meaningful one of its own) or scala.ScalaObject, is probably not what you want.
But the real question is why are you altering the semantics of the compiler-supplied equals?
Lastly, if there's any doubt in your mind (as evidenced by asking a question on Stack Overflow) when it comes to writing your own equals, it behooves you to read chapter 28, "Equality," of Programming in Scala. Hint: Writing truly correct equals is not trivial. That chapter is 26 pages long! (Note: I have only the 1st ed. at hand.)
Frankly, as infrequently as I need to write my own equals, I end up consulting that chapter every time I do.
Related
Why does this produce an error?
import scala.io.StdIn
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
def eval [T](flow: Flow[T]): T = flow match {
case Flow.Say(text) => println(text)
case Flow.Ask => StdIn.readLine // error here
}
18 | case Flow.Ask => StdIn.readLine
| ^^^^^^^^^^^^^^
| Found: String
| Required: T
(no error if the enum is left invariant on T)
But when the enum is replaced with a (seemingly equivalent?) ADT implemented with a sealed trait, no such error is reported:
sealed trait Flow[+A]
object Flow {
case class Say(text: String) extends Flow[Unit]
case object Ask extends Flow[String]
}
// match statement in eval works fine
We can look at the initial proposal which added enums to Scala 3 for some insight.
By the wording of this proposal, enum cases fall into three categories.
Class cases are those cases that are parameterized, either with a type parameter section [...] or with one or more (possibly empty) parameter sections (...).
Simple cases are cases of a non-generic enum class that have neither parameters nor an extends clause or body. That is, they consist of a name only.
Value cases are all cases that do not have a parameter section but that do have a (possibly generated) extends clause and/or a body.
Let's take a look at your enum declaration.
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
Now, simple cases are right out, because your enum is generic. Your Say is pretty clearly a class case, since it's parameterized. Ask, on the other hand, is non-generic and non-enumerated, so it's a value case. If we scroll down a bit, we see what value cases do
(6) A value case
case C extends <parents> <body>
expands to a value definition
val C = new <parents> { <body>; def enumTag = n; $values.register(this) }
where n is the ordinal number of the case in the companion object, starting from 0. The statement $values.register(this) registers the value as one of the enumValues of the enumeration (see below). $values is a compiler-defined private value in the companion object.
The bottom line here is that Ask is not defined as
object Ask extends Flow[String]
but more like
val Ask = new Flow[String]() { ... }
So your pattern match
case Flow.Ask => StdIn.readLine
is actually an equality check against the value Flow.Ask, rather than a type check against a singleton object. The former, unfortunately, proves nothing to the compiler about the generic type of your value, so it remains T, as opposed to specializing to String.
The rationale for this is provided on the same page
Objectives
...
Enumerations should be efficient, even if they define many values. In particular, we should avoid defining a new class for every value.
...
So it seems the Scala developers wanted to avoid the bloat that would result from an enum declaring tons of unparameterized values (i.e. a type that looks like an ordinary Java-style enum).
The simple solution, then, is to provide a parameter list
case Ask() extends Flow[String]
It's ever so slightly more cumbersome to work with, but it does define a new type, and then your pattern match
case Flow.Ask() => StdIn.readLine
will succeed
Consider the following code:
import scala.collection.mutable
case class A(b: Int) {
override def equals(obj: Any): Boolean = {
println("called")
obj match {
case o: A => b == o.b
case _ => false
}
}
}
val set = mutable.Set.empty[A]
val a1 = A(1)
set.add(a1)
println(set.contains(A(1)))
println(set.contains(A(2)))
Why the second call of set.contains doesn't print out "called"? Can someone explain how set identify two objects being equal?
As #LuisMiguelMejíaSuárez mentioned in the comments, A mutable Set is backed by a HashSet. It is well elaborated at What issues should be considered when overriding equals and hashCode in Java?
Whenever a.equals(b), then a.hashCode() must be same as b.hashCode().
This is because hashCode is a prior check, which when fails equals will definitely fail as well.
From What code is generated for an equals/hashCode method of a case class? we can conclude that for case classes hashCode and equals are overridden for case classes, using productPrefix which means the first set of elements in the case class. For example, if we have:
case class c(a: A)(b: B)
The hashCode and equals will be calculated only based on a. In your case, the hasCode method is calculated based on gender, which is different for Person(1) and Person(2). Therefore there is no need to check equals, which suppose to fail as well.
A tip from What issues should be considered when overriding equals and hashCode in Java?
In practice:
If you override one, then you should override the other.
Use the same set of fields that you use to compute equals() to compute hashCode().
I though if something like this makes sense in scala:
object CaseClassUnion extends App {
case class HttpConfig(bindUrl: String, port: String)
case class DbConfig(url: String, usr: String, pass: String)
val combined: HttpConfig with DbConfig = ???
//HttpConfig("0.0.0.0", "21") ++ DbConfig("localhost", "root", "root")
//would be nice to have something like that
}
At least this compiles... Is there a way, probably with macros magic to achieve union of two classes given their instances?
In zio I believe there is something like in reverse:
val live: ZLayer[ProfileConfiguration with Logging, Nothing, ApplicationConfiguration] =
ZLayer.fromServices[ProfileConfigurationModule.Service, Logger[String], Service] { (profileConfig, logger) => ???
where we convert ProfileConfiguration with Logging to function of ProfileConfigurationModule.Service, Logger[String] => Service
Several things.
When you have several traits combined with with Scala does a trait linearization to combine them into one class with a linear hierarchy. But that's true for traits which doesn't have constructors!
case class (which is not a trait) cannot be extended with another case class (at all) because that would break contracts like:
case class A(a: Int)
case class B(a: Int, b: String) extends A(a)
A(1) == B(1, "") // because B is A and their respective fields match
B(1, "") != A(1) // because A is not B
B(1, "").hashCode != A(1).hashCode // A == B is true but hashCodes are different!
which means that you cannot even generate case class combination manually. You want to "combine" them, use some product: a tuple, another case class, etc.
If you are curious about ZIO it:
uses traits
uses them as some sort of type-level trick to represent an unordered set of dependencies, where type inference would calculate set sum when you combine operations and some clever trickery to remove traits from the list using .provide to remove dependency from the set
ZLayers are just making these shenanigans easier
so and if you even pass there some A with B you either combined it yourself by using cake pattern, or you passed dependencies one by one. ZIO developer might never be faced with the problem of needing some macro to combine several case classes (.provide(combineMagically(A, B, C, D, ...)) as they could pass implementations of each dependency one by one (.provide(A).provide(B)) and the code underneath would never need the combination of these types as one value - it's just a compile-time trick that might never translate to the requirement of an actual value of type A with B with C with D ....
TL;DR: You cannot generate a combination of 2 case classes; ZIO uses compound types as some sort of type-level set to trace dependencies and it doesn't actually require creating values of the compound types.
Is there a better explanation than "this is how it works". I mean I tried this one:
class TestShortMatch[T <: AnyRef] {
def foo(t: T): Unit = {
val f = (_: Any) match {
case Val(t) => println(t)
case Sup(l) => println(l)
}
}
class Val(t: T)
class Sup(l: Number)
}
and compiler complaints:
Cannot resolve symbol 'Val'
Cannot resolve symbol 'Sup'
Of course if I add case before each of the classes it will work fine. But what is the reason? Does compiler make some optimization and generate a specific byte-code?
The reason is twofold. Pattern matching is just syntactic sugar for using extractors and case classes happen to give you a couple methods for free, one of which is an extractor method that corresponds to the main constructor.
If you want your example above to work, you need to define an unapply method inside objects Val and Sup. To do that you'd need extractor methods (which are only defined on val fields, so you'll have to make your fields vals):
class Val[T](val t: T)
class Sup(val l: Number)
object Val {
def unapply[T](v: Val[T]): Option[T] = Some(v.t)
}
object Sup {
def unapply(s: Sup): Option[Number] = Some(s.l)
}
And which point you can do something like val Val(v) = new Val("hi"). More often than not, though, it is better to make your class a case class. Then, the only times you should be defining extra extractors.
The usual example (to which I can't seem to find a reference) is coordinates:
case class Coordinate(x: Double, val: Double)
And then you can define a custom extractors like
object Polar {
def unapply(c: Coordinate): Option[(Double,Double)] = {...}
}
object Cartesian {
def unapply(c: Coordinate): Option[(Double,Double)] = Some((c.x,c.y))
}
to convert to the two different representations, all when you pattern match.
You can use pattern matching on arbitrary classes, but you need to implement an unapply method, used to "de-construct" the object.
With a case class, the unapply method is automatically generated by the compiler, so you don't need to implement it yourself.
When you write match exp { case Val(pattern) => ... case ... }, that is equivalent to something like this:
match Val.unapply(exp) {
case Some(pattern) =>
...
case _ =>
// code to match the other cases goes here
}
That is, it uses the result of the companion object's unapply method to see whether the match succeeded.
If you define a case class, it automatically defines a companion object with a suitable unapply method. For a normal class it doesn't. The motivation for that is the same as for the other things that gets automatically defined for case classes (like equals and hashCode for example): By declaring a class as a case class, you're making a statement about how you want the class to behave. Given that, there's a good chance that the auto generated will do what you want. For a general class, it's up to you to define these methods like you want them to behave.
Note that parameters for case classes are vals by default, which isn't true for normal classes. So your class class Val(t: T) doesn't even have any way to access t from the outside. So it isn't even possible to define an unapply method that gets at the value of t. That's another reason why you don't get an automatically generated unapply for normal classes: It isn't even possible to generate one unless all parameters are vals.
Squeryl defines a trait KeyedEntity which overrides equals, checking for several conditions in an if and calling super.equals in the end. Since super is Object, it will always fail.
Consider:
trait T { override def equals(z: Any):Boolean = super.equals(z)} }
case class A(a: Int) extends T
val a = A(1); val b = A(1)
a==b // false
Thus, if you declare
case class Record(id: Long, name: String ...) extends KeyedEntity[Long] { ... }
-- and you create several Record instances but do not persist them, their comparison will break. I found this by implementing both Salat and Squeryl back ends for the same class, and then all Salat tests fail since isPersisted from KeyedEntity is false.
Is there a design whereby KeyedEntity will preserve case class equality if mixed into a case class? I tried self-typing and parameterizing BetterKeyedEntity[K,P] { self: P => ... } for the case class type as P but it causes infinite recursion in equals.
As things stand right now, super is Object so the final branch of the overridden equals in KeyedEntity will always return false.
The structural equality check usually generated for case classes seems not to be generated if there is an equals override. However some subtleties have to be noted.
Mixing the concept of id-based equality falling back to structural equality might not be a good idea, since I can imagine that it may lead to subtle bugs. For example:
x: A(1) and and y: A(1) are not yet persisted, so they are equal
then they get persisted, and since they are separate objects, the persistence framework may persist them as separate entities (I don't know Squeryl, maybe not an issue there, but this is a thin line to walk)
after persisting, they are suddenly not equal since the id differs.
Even worse, if x and y get persisted to the same id, the hashCode will differ before and after persisting (the source shows that if persisted it is the hashCode of the id). This breaks immutability, and will lead to very bad behavior (when put in maps for example). See this gist in which I demonstrate the assert failing.
So don't mix structural and id-based equality implicitly. Also see this explained in the context of Hibernate.
Typeclasses
It have to be noted that others pointed out (ref needed) that the concept of method-based equality is flawed, for such reasons (there is not only one way two thing can be equal). Therefore you can define a typeclass which describes Equality:
trait Eq[A] {
def equal(x: A, y: A): Boolean
}
and define (possibly multiple) instances of that typeclass for your classes:
// structural equality
implicit object MyClassEqual extends Eq[MyClass] { ... }
// id based equality
def idEq[K, A <: KeyedEntity[K]]: Eq[A] = new Eq[A] {
def equal(x: A, y: A) = x.id == y.id
}
then you can request that things are members of the Eq typeclass:
def useSomeObjects[A](a: A, b: A)(implicit aEq: Eq[A]) = {
... aEq.equal(a, b) ...
}
So you can decide which notion of equality to use by importing the appropriate typeclass in scope, or passing the typeclass instance directly as in useSomeObjects(x, y)(idEq[Int, SomeClass])
Note that you might also need a Hashable typeclass, similarly.
Autogenerating Eq instances
This situation is pretty similar to the Scala stdlib's scala.math.Ordering typeclass. Here is an example for auto-deriving structural Ordering instances for case classes using the excellent shapeless library.
The same would easy to be done for Eq and Hashable.
Scalaz
Note that scalaz has Equal typeclass, with nice pimp patterns with which you can write x === y instead of eqInstance.equal(x, y). I'm not aware it has Hashable typeclass, yet.