Scala:类型推断遗漏了一些东西

Scala: Type inference missing something

我想在以下情况下实现某种类型安全。

基本上,我有不同类型的请求存储在数据库中,它们的类型用一些字符串代码标识。出于商业原因,此代码 匹配 class 名称。

每种类型的请求都包含某种有效载荷,有效载荷的类型直接取决于请求的类型。

这是我目前所取得成就的简化版本:

trait Request[Payload] {
  def metadata: String // Not relevant
  def payload: Payload
}

case class RequestWithString(override val metadata: String, override val payload: String) extends Request[String]

case class AnotherTypeOfRequestWithString(override val metadata: String, override val payload: String) extends Request[String]

case class RequestWithInt(override val metadata: String, override val payload: Int) extends Request[Int]

object Request {
  def apply(code: String)(metadata: String, payload: Any): Request[_] = code match {
    case "S" => RequestWithString(metadata, payload.asInstanceOf[String])
    case "S2" => AnotherTypeOfRequestWithString(metadata, payload.asInstanceOf[String])
    case "I" => RequestWithInt(metadata, payload.asInstanceOf[Int])
  }
}

这并不令人满意,因为我希望 Scala 推断有效负载的类型以避免强制转换,以及返回值的(参数化)类型。

我正在寻找的是这样的东西:

object Request {
  def apply[P, R <: Request[P]](code: String)(metadata: String, payload: P): R = code match {
    case "S" => RequestWithString(metadata, payload)
    case "S2" => AnotherTypeOfRequestWithString(metadata, payload)
    case "I" => RequestWithInt(metadata, payload)
  }
}

但这似乎不起作用,我无法摆脱一些类型不匹配的错误:

 found   : P
 required: String
               case "S" => RequestWithString(metadata, payload)
                                                       ^

在这种情况下,Scala 不应该推断 P 是 String 吗?我错过了什么?

我可以看到一些重大改进。让我们从头开始,首先我们从不在抽象成员的特征中使用 val,看 here.

trait Request[Payload] {
  def metadata: String // Not relevant
  def payload: Payload
}

现在让我们看这里:

object Request {
  def apply[P, R <: Request[P]](code: String)(metadata: String, payload: P): R = code match {
    case "S" => RequestWithString(metadata, payload)
    case "I" => RequestWithInt(metadata, payload)
  }
}

你误解了 P <: Request[P] 的意思,这是一个 f-bounded 类型的多态参数,用于所谓的类型细化,例如 return 调用后最具体的包装器类型在类型上限上定义的方法,例如在 Request return RequestWithInt 上有方法,而不仅仅是 Request。就您而言,我认为您无论如何都没有选择正确的方法。

您可以将它用于将 RequestWithStringRequestWithInt 实例作为参数或类似参数的方法。

现在,对于您的情况,您应该做的是将 ADT 用于您的请求类型。类似于 RequestEncoder.

trait RequestEncoder[T] {
  def encode(obj: T): String
  def decode(obj: String): T
}

object RequestEncoder {
  implicit val intEncoder = new RequestEncoder[Int] {
    def encode(obj: Int): String = obj.toString
    def decode(source: String): Int = source.toInt
  }
}

trait Request[Payload : RequestEncoder] {
  def metadata: String // Not relevant
  def payload(source: Payload): String = implicitly[RequestEncoder[Payload]].encode(source)
}

将匹配的决策逻辑移至类型类:

// this typeclass holds the logic for creating a `Request` for
// a particular payload
sealed abstract class RequestPayloadType[T](val create: (String, T) => Request[T])
object RequestPayloadType {
  implicit object StringPayloadType extends RequestPayloadType[String] (RequestWithString.apply)
  implicit object IntPayloadType extends RequestPayloadType[Int] (RequestWithInt.apply)
}

object Request {
  def apply[P:RequestPayloadType](metadata: String, payload: P): Request[P] = 
    implicitly[RequestPayloadType[P]].create(metadata, payload)
}

scala 中的常见模式:将需要特定类型知识的代码移动到具有该知识的编译单元。

请记住,有单独的请求类,只有一个参数化的请求可能更干净:

case class Request [P:RequestPayloadType](metadata: String, payload: P) {
  // delegate any code that needs to know the type to `implicitly[RequestPayloadType[T]]...`
}

sealed trait RequestPayloadType[T] {
  // specify here code that needs to know the actual type, i.e: 
  // def encode (value: T): String  // abstract 
  // def decode (value: String): T  // abstract
} 
object RequestPayloadType {
  implicit object StringPayloadType extends RequestPayloadType[String] {
    // implement here any `String` specific code, .i.e:
    // def encode (s: String) = s
    // ...
  }
  implicit object IntPayloadType extends RequestPayloadType[Int] {
    // implement here any `Int` specific code, .i.e:
    // def encode (i: Int) = i.toString
    // ...
  }
}