Cats: Add Chain equals & hashCode methods (via Chain.iterator?)

Created on 28 Sep 2018  ·  7Comments  ·  Source: typelevel/cats

Currently Chain[A] only defines === operator and therefore different Chain instances with "same" contents are not .equals to each other:

import cats.data.Chain
import cats.implicits._

(Chain.one(1) |+| Chain.one(2) |+| Chain.one(3)) == Chain.fromSeq(List(1, 2, 3)) // false
(Chain.one(1) |+| Chain.one(2) |+| Chain.one(3)) === Chain.fromSeq(List(1, 2, 3)) // true

Not only this leads to confusion, but also could lead to bugs if Chain[A] used in Set or as Map keys, or in Eq.fromUniversalEquals.

low-hanging fruit

Most helpful comment

I'll give this a try.

All 7 comments

So, for this do we want a == method on Chain ?

You can't define ==, but it's desugared to equals :)

I _think_ that it should be pretty straightforward to implement equals in terms of === and Eq.fromUniversalEquals; and hashCode in terms of Chain.iterator, StaticMethods.orderedHash, and Hash.fromUniversalHashCode.

🙋🏻‍♂️

Chiming in with a vote — this was causing my ScalaTest assertResult check to fail in the context of ValidatedNec.

Welcome to Scala 2.12.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_192).
Type in expressions for evaluation. Or try :help.

scala> import cats.data.ValidatedNec
import cats.data.ValidatedNec

scala> type ExampleValidatedNec = ValidatedNec[String, Int]
defined type alias ExampleValidatedNec

scala> import cats.data._
import cats.data._

scala> import cats.implicits._
import cats.implicits._

scala> val validatedInt1 = Validated.Invalid("foo").toValidatedNec
validatedInt1: cats.data.ValidatedNec[String,Nothing] = Invalid(Chain(foo))

scala> val validatedInt2 = Validated.Invalid("bar").toValidatedNec
validatedInt2: cats.data.ValidatedNec[String,Nothing] = Invalid(Chain(bar))

scala> case class TwoInts(int1: Int, int2: Int)
defined class TwoInts

scala> val exampleValidatedNec2 = (validatedInt1, validatedInt2).mapN(TwoInts.apply)
exampleValidatedNec2: cats.data.ValidatedNec[String,TwoInts] = Invalid(Chain(foo, bar))

scala> val expectedNecOfTwo = NonEmptyChain("foo", "bar")
expectedNecOfTwo: cats.data.NonEmptyChain[String] = Chain(foo, bar)

scala> exampleValidatedNec2.toEither.left.get == expectedNecOfTwo // expect true
res0: Boolean = true

scala> case class ThreeInts(int1: Int, int2: Int, int3: Int)
defined class ThreeInts

scala> val validatedInt3 = Validated.Invalid("baz").toValidatedNec
validatedInt3: cats.data.ValidatedNec[String,Nothing] = Invalid(Chain(baz))

scala> val exampleValidatedNec3 = (validatedInt1, validatedInt2, validatedInt3).mapN(ThreeInts.apply)
exampleValidatedNec3: cats.data.ValidatedNec[String,ThreeInts] = Invalid(Chain(foo, bar, baz))

scala> val expectedNecOfThree = NonEmptyChain("foo", "bar", "baz")
expectedNecOfThree: cats.data.NonEmptyChain[String] = Chain(foo, bar, baz)

scala> exampleValidatedNec3.toEither.left.get == expectedNecOfThree // expect true
res1: Boolean = false

scala> exampleValidatedNec3.toEither.left.get === expectedNecOfThree // expect true
res2: Boolean = true

So it looks like somehow the equality check works for two or fewer elements, but not for three. Obviously I will change my test to use ===, but it'd be great to have Object.equals overridden and functioning here.

👍 on @ceedubs' suggestion.

I'll give this a try.

2690 submitted for this

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mcanlas picture mcanlas  ·  4Comments

diesalbla picture diesalbla  ·  4Comments

Atry picture Atry  ·  5Comments

chuwy picture chuwy  ·  4Comments

julien-truffaut picture julien-truffaut  ·  3Comments