How to handle Option with an encoder typeclass in scala

人盡茶涼 提交于 2021-02-10 22:48:07

问题


I have a classic Encoder typeclass.

trait Encoder[A] {
  def encode(a: A): String
}

I have two questions

Question 1 : where does the divergence comes from :

[error] … diverging implicit expansion for type sbopt.Test.Encoder[None.type]
[error] starting with value stringEncoder in object Test
[error]   show(None)
implicit val stringEncoder = new Encoder[String] {
  override def encode(a: String): String = a
}
implicit def optionEncoder[A: Encoder]: Encoder[Option[A]] =
  (a: Option[A]) => {
    val encoderA = implicitly[Encoder[A]]
    a.fold("")(encoderA.encode)
  }
implicit def someEncoder[A: Encoder]: Encoder[Some[A]] =
  (a: Some[A]) => {
    val encoderA = implicitly[Encoder[A]]
    encoderA.encode(a.get)
  }
implicit def noneEncoder[A: Encoder]: Encoder[None.type] =
  (_: None.type) => ""

def show[A: Encoder](a: A) = println(implicitly[Encoder[A]].encode(a))

show(None)

Question 2 : I saw in circe that the Encoder is not contravariant. What are the pros and cons ?

trait Encoder[-A] {
  def encode(a: A): String
}

implicit val stringEncoder: Encoder[String] = (a: String) => a

implicit def optionEncoder[A: Encoder]: Encoder[Option[A]] =
  (a: Option[A]) => {
    val encoderA = implicitly[Encoder[A]]
    a.fold("")(encoderA.encode)
  }

def show[A: Encoder](a: A) = println(implicitly[Encoder[A]].encode(a))

show(Option("value"))
show(Some("value"))
show(None)

回答1:


Regarding 1.

Your definition of noneEncoder is not good. You have an extra context bound (and even extra type parameter).

With

implicit def noneEncoder/*[A: Encoder]*/: Encoder[None.type] =
  (_: None.type) => ""

it compiles:

show[Option[String]](None)
show[None.type](None)
show(None)

Your original definition of noneEncoder meant that you had an instance of Encoder for None.type provided you had an instance for some A (not constrained i.e. to be inferred). Normally this works if you have the only implicit (or at least the only higher-priority implicit). For example if there were only stringEncoder and original noneEncoder then show[None.type](None) and show(None) would compile.

Regarding 2.

PROS. With contravariant Encoder

trait Encoder[-A] {
  def encode(a: A): String
}

you can remove someEncoder and noneEncoder, optionEncoder will be enough

show(Some("a"))
show[Option[String]](Some("a"))
show[Option[String]](None)
show[None.type](None)
show(None)

CONS. Some people believe that contravariant type classes behave counterintuitively:

https://github.com/scala/bug/issues/2509

https://groups.google.com/g/scala-language/c/ZE83TvSWpT4/m/YiwJJLZRmlcJ

Maybe also relevant: In scala 2.13, how to use implicitly[value singleton type]?



来源:https://stackoverflow.com/questions/64027726/how-to-handle-option-with-an-encoder-typeclass-in-scala

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!