2017-04-12 44 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