2017-04-12 47 views
1

我想在以下情况下实现某种类型的安全。Scala:类型推断丢失了一些东西

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

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

下面是我迄今取得的简化版本:

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]) 
    } 
} 

这不是令人满意的,因为我想斯卡拉推断有效负载的类型,以避免铸造,以及(参数化)类型的返回值。

我所寻找的是类似的东西:

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吗?我错过了什么?

+0

你在每个类型的请求做什么进一步的处理? –

+0

@YuvalItzchakov:带有ReactiveMongo的MongoDB存储,然后从有效负载和远程数据中编写人类可读的文本,以便将文本发送到远程系统。 我需要读取/写入MongoDB,Request.apply函数的主要目的是在BSONReader [Request]中调用。 –

+0

现在的答案帮了很大忙。但是,我编辑了一下,使其更清楚,请求的类型不仅取决于有效负载的类型。可能有几个具有相同类型Payload的Request子类。 –

回答

2

移动匹配判定逻辑的类型类:

// 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) 
} 

普通的图案在阶:移动,需要某些类型的知识的代码,其具有的知识编译单元。

请记住,这可能是清洁剂有单独的请求类,只是有一个单一的参数之一:

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 
    // ... 
    } 
} 
2

我可以看到一些重大改进。让我们从头开始,首先我们从不使用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-限定型多态PARAM,它是用来做什么的被称为类型细化,例如返回最具体的包装类型调用上限类型定义的方法后,例如Request上的方法返回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) 
} 
+0

我编辑了特性以避免使用vals,因为这与真正的问题无关。谢谢。我不明白编码器的用途,为什么我要编码/解码字符串的有效载荷? –

+0

@ K.C。我只是向你展示一种方法,阅读有关类型类和如何使用它们。关键是你应该在'Request'上定义你想要的任何方法,并且使用类型类方法来实现它们的每种类型。 – flavian