2017-04-12 17 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]) 
    } 
} 

をこれは私がScalaはキャストを避けるためにペイロードの型を推論したい、と(parametered)タイプとして満足ではありません。ここで

は、私がこれまで達成してきたものの簡易版であります戻り値の私が探しています何

はそのようなものです:

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) 
                ^

Shouldn」この場合、PはStringであるとScalaは推測していますか?私は何が欠けていますか?

+0

? –

+0

@YuvalItzchakov:ReactiveMongoを使用したMongoDBストレージ。ペイロードとリモートデータから人間が読めるテキストを作成し、テキストをリモートシステムに送信します。 MongoDBを読み書きする必要がありますが、Request.apply関数の主な目的はBSONReader [Request]で呼び出されることです。 –

+0

現在の回答が多くを助けています。しかし、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) 
} 

Scalaで一般的なパターン:その知識を有するコンパイル単位に、特定のタイプの知識を必要とするコードを移動します。

それはないにきれいかもしれないが、覚えておいては、個々の要求クラスをしており、ただ一つのパラメータ化1を持っている:あなたは、要求の種類ごとに、さらには何の処理を行っている

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のメソッドを持っている場合は、単にRequestの代わりにRequestWithIntが返されます。あなたの場合、私はあなたが正しいアプローチをとらえているとは思わない。

RequestWithStringRequestWithIntの両方のインスタンスをparamsまたはそのようなものとして使用する方法に使用します。

あなたの場合は、あなたのリクエストタイプに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

実際の問題には関係ないので、valを使用しないように特性を編集しました。ありがとう。私はエンコーダの目的を理解していない、なぜペイロードをエンコード/デコードする必要がありますか? –

+0

@ K.C。私は単純にあなたにアプローチを示し、タイプメスとその使い方を読んでいました。ポイントは、あなたが 'Request'で一般的に望むメソッドを定義し、typeclassアプローチを使ってそれらをタイプごとに実装する必要があることです。 – flavian