Search code examples
scalascala-cats

Scala cats: problem with implicits when trying to use `===` syntax for `Eq` trait


Aloha! :)

I'm not happy about running all the time to the stackoverflow forums to get some help with another piece of strangeness scala/cats has thrown at me. Problem is: There seems to be no really useful documentation, only some worthless - at least for me - repl lines.

Could please somebody of you point to some useful documentation? Some real code? Not just lines in the repl?

Here I just tried to work with scala/cats Eq and Show typeclasses... What the hell am I doing wrong?

The class:

package org.hudelundpfusch.utilites.decisions.data

import cats.Show
import cats.kernel.Eq

case class Fact[+T <: Any](name: String, value: T)
  extends Equals {

  override def canEqual(that: Any): Boolean = that match {
    case _: Fact[_] => true
    case _          => false
  }

  override def equals(other: Any): Boolean = other match {
    case that: Fact[_] =>
      (that canEqual this) &&
        name == that.name &&
        value == that.value
    case _ => false
  }

  override def hashCode(): Int = {
    val state = Seq(name, value)
    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
  }

  override def toString = s"Fact(name=$name, value=$value)"

}

case object Fact {

  implicit val factEq: Eq[Fact[_]] = Eq.fromUniversalEquals[Fact[_]] // Neither of this works

//  implicit def factEq: Eq[Fact[_]] = new Eq[Fact[_]] {
//    def eqv(x: Fact[_], y: Fact[_]): Boolean = (x != null, y != null) match {
//      case (true, _)  => x.equals(y)
//      case (_, true)  => y.equals(x)
//      case _          => true
//    }
//  }

  implicit def factShow[T]: Show[Fact[T]] = (t: Fact[T]) => t.toString // Example calls for 'implicit val factShow[Fact[_]]' but that doesn't work

}

And the big surprise:

package org.hudelundpfusch.utilites.decisions

import cats._
import cats.data._
import cats.syntax._
import cats.implicits._
import cats.implicits.eq
import com.typesafe.scalalogging.LazyLogging
import org.hudelundpfusch.utilites.decisions.data.Fact
import org.hudelundpfusch.utilites.decisions.data.Fact._
// Tried to import everything that came to my mind to make the stuff working

object Fuddel
  extends App
    with LazyLogging {

  logger.info("Let's start to fuddel!")
  this.fuddel()
  logger.info("Enough with fuddling!")

  def fuddel(): Unit = {
    val fact1: Fact[String] = Fact[String]("FactName", "FactValue")
    println(s"${fact1.show}")
    val fact2: Fact[String] = Fact[String]("FactName", "FactValue")
    println(s"${fact2.show}")

    println(s"${fact1.equals(fact2)}")
    println(s"${fact1 == fact2}")
//    println(s"${fact1 === fact2}") // Not resolved...According to the repl example this should work with implicits imported
    println(s"${fact1 eq fact2}") // False? Oh joy! Thanks to the great repl example!
  }

}

So please, is there any documentation not beeing worthless?

Thanks in advance

Have a better day than me

Alex


Solution

  • 1. This here compiles just fine (I removed your package name and logging dependency):

    import cats.Show
    import cats.kernel.Eq
    
    case class Fact[+T](name: String, value: T) extends Equals {
    
      override def canEqual(that: Any): Boolean = that match {
        case _: Fact[_] => true
        case _          => false
      }
    
      override def equals(other: Any): Boolean = other match {
        case that: Fact[_] => true // TODO: replaced, irrelevant
        case _ => false
      }
    
      override def hashCode(): Int = {
        val state = Seq(name, value)
        state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
      }
    
      override def toString = s"Fact(name=$name, value=$value)"
    }
    
    case object Fact {
      implicit def factEq[A]: Eq[Fact[A]] = Eq.fromUniversalEquals[Fact[A]]
      implicit def factShow[T]: Show[Fact[T]] = (t: Fact[T]) => t.toString
    }
    

    Note the universal quantification instead of wildcard in factEq[A]. Then in Fuddel.scala:

    import cats.syntax.show._
    import cats.syntax.eq._
    import Fact._
    
    object Fuddel
      extends App {
    
      this.fuddel()
    
      def fuddel(): Unit = {
        val fact1: Fact[String] = Fact[String]("FactName", "FactValue")
        println(s"${fact1.show}")
        val fact2: Fact[String] = Fact[String]("FactName", "FactValue")
        println(s"${fact2.show}")
    
        println(s"${fact1.equals(fact2)}")
        println(s"${fact1 == fact2}")
        println(s"${fact1 === fact2}")
        println(s"${fact1 eq fact2}")// must be false, different instances
      }
    
    }
    

    Note that the eq is a method that is available on every object in Scala, there is no way to override it.


    2. I'd recommend to read Welsh, Gurnell "Scala with Cats". Scaladoc is good too, but you cannot navigate it effectively until you read the introductory chapter about the organization of packages and implicits in the cats library.