2016-09-22 9 views
0

スカラコンパイラがスーパークラスに渡された型パラメータを推論できないので、私は回避策を考え出すことができます。回避策の提案も大歓迎です!ここに私が執着しているものの巧妙な例があります(問題を説明するコードのコメント)。スカラーコンパイラがスーパークラスの型パラメータを推論しないのはなぜですか?

コードもscala fiddleにあります。

/** A Svc is a function that responds to requests 
    * @tparam Req[_] a request ADT whose instances specify their response type 
    */ 
trait Svc[Req[_]] { 
    def apply[Resp](req: Req[Resp]): Resp 
} 

/** Service request ADT */ 
sealed trait MyReq[_] 
// two requests have the same response type of String (i.e. MyReq[String]): 
case class GetString(id: String) extends MyReq[String] 
case class GetAltString(id: String) extends MyReq[String] 
// this one is the only MyReq[Int] 
case class GetInt(id: String) extends MyReq[Int] 

/** Type class for marshalling a response for a concrete request type. 
    * This lets us handle marshalling differently for different requests 
    * that have the same response type (such as GetString and GetAltString above). 
    * 
    * @tparam ReqImpl concrete MyReq type. This is required to enforce unique marshaller 
    * per request when there are mutliple request types with the same response type. 
    */ 
trait ReqMarshaller[ReqImpl <: MyReq[Resp], Resp] { 
    def marshal(r: Resp): String 
} 

class MySvc extends Svc[MyReq] { 
    // this apply function compiles and works just fine. 
    override def apply[Resp](req: MyReq[Resp]): Resp = req match { 
    case GetString(id) => id 
    case GetAltString(id) => id + id 
    case GetInt(id) => id.length 
    } 

    // This is the problem. I want to specify the request is a subclass so 
    // we get the specific marshaller for the request type and avoid 
    // ambiguous implicit errors. 
    // However, the Resp type parameter is always inferred as Nothing 
    // instead of the correct response type. 
    def marshal[ReqImpl <: MyReq[Resp], Resp](req: ReqImpl)(
    implicit 
    marshaller: ReqMarshaller[ReqImpl, Resp] 
): String = marshaller.marshal(apply(req)) 

    // this method is just here to show that it won't work as a solution 
    // because it doesn't work when there are multiple request types with 
    // the same response type (causes ambiguous implicits errors) 
    def marshalGeneric[Resp](req: MyReq[Resp])(
    implicit 
    marshaller: ReqMarshaller[_ <: MyReq[Resp], Resp] 
): String = marshaller.marshal(apply(req)) 
} 

implicit val getIntMarshaller: ReqMarshaller[GetInt, Int] = new ReqMarshaller[GetInt, Int] { 
    def marshal(i: Int): String = (i * i).toString 
} 

implicit val getStrMarshaller: ReqMarshaller[GetString, String] = new ReqMarshaller[GetString, String] { 
    def marshal(s: String): String = s 
} 

implicit val getAltStrMarshaller: ReqMarshaller[GetAltString, String] = new ReqMarshaller[GetAltString, String] { 
    def marshal(s: String): String = s + s 
} 

val svc = new MySvc 

val myLength = svc(GetInt("me")) // 2 
println(s"myLength: $myLength") 

svc.marshalGeneric(GetInt("me")) // compiles and works 
//svc.marshal(GetInt("me")) // fails to compile due to infering Resp type as Nothing 
//svc.marshalGeneric(GetAltString("me")) // fails to compile because of ambiguous implicits 
+0

あなたはscastieを作ることができますか? – nafg

+0

@nafg提案に感謝します。私は代わりにスカラ・フィドルを作った:https://scalafiddle.io/sf/bGtDio1/0 –

+0

これはあなたを助けるかもしれない? http://stackoverflow.com/questions/6682824/how-can-i-combine-the-typeclass-pattern-with-subtyping –

答えて

4

問題は、Scalaは一度代わりの最初のReqImplを推定し、そのからRespを得ることで、両方のReqImplResp型パラメータを推測しようとしていることです。 Respは実際にはパラメータリストには表示されないので、Nothingに推論され、型境界が違反しているというScala通知が推測されます。この問題を回避するには、(私が最初にそれを見たところ、私は覚えていない)reqと同等のタイプを与えることですが、明示的Respに依存して1:

def marshal[ReqImpl <: MyReq[Resp], Resp](req: ReqImpl with MyReq[Resp])(
    implicit marshaller: ReqMarshaller[ReqImpl, Resp] 
): String = marshaller.marshal(apply(req)) 

svc.marshal(GetInt("me"))は今コンパイルします。

+0

これは、コンパイラが 'Resp'型を' Nothing'に推論することを解決しますが、私は'svc.marshalGeneric(GetAltString(" me "))'のあいまいな暗黙的な意味のために、コンパイラー・エラーを受け取ります。しかし、あなたは私の質問に答えました。ありがとうございました! –

+0

'marshalGeneric(GetAltString(" me "))'の場合、単純にあいまいな意味があります: 'getStrMarshaller'と' getAltStrMarshaller'の両方が適切です。固定されたマーシャルと違う何かをしたいですか? –

+0

もちろん、あなたは正しいです。私は 'marshalGeneric(GetAltString(" me "))'をマーシャル(GetAltString( "me")) 'に戻すのを忘れていました。再度、感謝します! –

0

は、私はあなたのSVC特性にReqのTypeパラメーターとあなたのapply機能のtypeのparamとの間の関係をキャプチャする必要があると思います。それに応じて残りの部分を修正することができます。

trait Svc[Req[_ <: XX], XX] { 
    def apply[Resp <: XX](req: Req[Resp]): Resp 
} 
0

これを行う1つの方法は、ReqImplがパラメータ化されたタイプ(Type infered to Nothing in Scala)であることを明示することです。

def marshal[ReqImpl[Resp] <: MyReq[Resp], Resp](req: ReqImpl[Resp])(
    implicit 
    marshaller: ReqMarshaller[ReqImpl[Resp], Resp] 
): String = marshaller.marshal(apply(req)) 

をしかし、このアプローチには二つの問題があります:あなたのケースでは、それは次のようになります。

(1)Scalaは一種の意味を理解MyReq[Int]、としてRepImplの種類を推測しますsvc.marshal(GetInt("me"))では、 ReqMarshaller[GetInt, Int]は一致しません。 (2)今、あなたはすぐに別の問題を抱えている、あなたは同時に2つのReqMarshaller[MyReq[String], String]を定義することはできません

implicit val getIntMarshaller = new ReqMarshaller[MyReq[Int], Int] { 
    def marshal(i: Int): String = (i * i).toString 
} 

:だから、としてそれを定義する必要があります。同じタイプのパラメータを持つ2つのエンドポイントを定義するのは良い考えではないかもしれません(ちょうど推測ですが、ここには適合しないものもありますが、Alexey Romanovの解決策ではうまくいきません)。

UPDATE

(1)ReqMarshaller共変することによって解決される:

trait ReqMarshaller[+ReqImpl <: MyReq[Resp], Resp] { ... 

(2)は依然として曖昧暗黙で失敗します。

関連する問題