【问题标题】:Organizing Scala implicits associated with a type组织与类型关联的 Scala 隐式
【发布时间】:2019-04-30 11:31:40
【问题描述】:

我想介绍一些类型来表示更大类型中字段的可能值。该字段需要能够对 JSON 进行编码/解码,还需要能够写入/读取到数据库中。

我还是 Scala 的新手,我想要的类型是 sum 类型 Status = NotVerified | Correct | Wrong。由于我希望有一个与每个构造函数关联的字符串表示,我创建了一个带有String 参数的密封案例类,然后是扩展该案例类的对象。为了能够编码/解码,我还需要隐式,但我不确定如何构造它。我可以将它们放在对象内部的一个新对象中,如下所示:

sealed case class Status(name: String)
object Status {
  object NotVerified extends Status("not_verified")
  object Correct extends Status("correct")
  object Wrong extends Status("wrong")

  object implicits {
    implicit val encodeStatusJson: Encoder[Status] =
      _.name.asJson
    implicit val decodeStatusJson: Decoder[Status] =
      Decoder.decodeString.map(Status(_))

    implicit val encodeStatus: MappedEncoding[Status, String] =
      MappedEncoding[Status, String](_.name)

    implicit val decodeStatus: MappedEncoding[String, Status] =
      MappedEncoding[String, Status](Status(_))
  }
}

…然后在需要的地方明确地import 这些,但那是相当…明确的。

组织这种类型的集合+隐式的好方法是什么?

【问题讨论】:

  • 您实现Status 的方式存在问题,因为您可以创建Status("not_verified"),但类型将是Status 而不是NotVerified,并且match 类型不会给出正确的结果。你可以制作Statusabstract,但我怀疑这会破坏Decoder
  • @Tim 哦。你说得对!谢谢!我不真的想要这里的子类型。我想我应该使用密封特征,然后扩展它的案例类?但随后 quill/circe 想要的解码器/编码器将非常重复……
  • 是的,这是进行简单枚举的正确方法。使 Status 抽象并创建一个伴随对象 Statusdef apply(name: String): Status = ... 匹配字符串并返回适当的子类。然后Status("not_verified") 将返回正确的底层类型。

标签: scala implicit circe quill.io


【解决方案1】:

常见的做法是定义一个sealed trait

sealed trait Status {
  def name: String
}

object Status {
  case object NotVerified extends Status {
    val name = "not_verified"
  }
  case object Correct extends Status {
    val name = "correct"
  }
  case object Wrong extends Status {
    val name = "wrong"
  }
}

或者sealed abstract class,在当前的 Scala 版本中可能看起来更好:

sealed abstract class Status(val name: String)

object Status {
  case object NotVerified extends Status("not_verified")
  case object Correct extends Status("correct")
  case object Wrong extends Status("wrong")
}

为了避免需要导入隐式,它们可以直接放在类型的伴生对象中。另请参阅问题Where does Scala look for implicits? 了解更多详细信息,尤其是类型的伴随对象部分。

是的,为这样的枚举定义隐式很容易重复。您必须求助于反射或宏。我推荐使用 Enumeratum 库,它还集成了 Circe 和 Quill。 以下是 Circe 的示例:

import enumeratum.values._

sealed abstract class Status(val value: String) extends StringEnumEntry {
  def name: String = value
}

object Status extends StringEnum[Status] with StringCirceEnum[Status] {
  val values = findValues

  case object NotVerified extends Status("not_verified")
  case object Correct extends Status("correct")
  case object Wrong extends Status("wrong")
}

您可以在不明确定义任何编码器/解码器或从Status 导入任何内容的情况下使用它:

scala> import io.circe.syntax._

scala> val status: Status = Status.Correct
status: Status = Correct

scala> status.asJson
res1: io.circe.Json = "correct"

scala> Decoder[Status].decodeJson(Json.fromString("correct"))
res2: io.circe.Decoder.Result[Status] = Right(Correct)

【讨论】:

    【解决方案2】:

    如果您添加一个apply 方法,您可以从String 创建适当的Status,这将使Decoder 正常工作。并使Status抽象

    sealed abstract class Status(name: String)
    
    object Status {
      object NotVerified extends Status("not_verified")
      object Correct extends Status("correct")
      object Wrong extends Status("wrong")
    
      def apply(name: String): Status = name match {
        case "not_verified" => NotVerified
        case "correct" => Correct
        case _ => Wrong
      }
    }
    

    我认为你现有的隐式仍然可以工作,但我不知道那些特定的库......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-21
      • 1970-01-01
      • 2016-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多