2015-09-24 10 views
6
[error] test.scala:31: ambiguous implicit values: 
[error] both method taggedQueryParamDecoder in trait ExternalInstances0 of type [A, T](implicit evidence$2: org.http4s.QueryParamDecoder[A])org.http4s.QueryParamDecoder[[email protected]@[A,T]] 
[error] and method iiQueryParamDecoder in trait ExternalInstances1 of type [B](implicit ii: foo.InvariantInstances[B])org.http4s.QueryParamDecoder[B] 
[error] match expected type org.http4s.QueryParamDecoder[[email protected]@[String,foo.tags.Social]] 
[error] implicitly[QueryParamDecoder[String @@ Social]] 
[error]   ^

Importazione instances._; instances estende ExternalInstances1 e ExternalInstances1 estende ExternalInstances0. A causa di questa ereditarietà, mi aspetterei che i membri di ExternalInstances1 vincano oltre ExternalInstances0, piuttosto che cedere a un'ambiguità.perché ottengo un errore "implicito ambiguo" nonostante abbia implicito priorità per gli impliciti?

Perché sta succedendo e come posso risolverlo? Grazie.

La fonte è a http://scastie.org/12233, di seguito riprodotto:

/*** 
scalaVersion := "2.11.7" 

libraryDependencies += "org.http4s" %% "http4s-core" % "0.10.0" 

resolvers ++= Seq(
    "tpolecat" at "http://dl.bintray.com/tpolecat/maven", 
    "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", 
    Resolver.sonatypeRepo("releases") 
) 

libraryDependencies += "org.tpolecat" %% "doobie-core" % "0.2.2" 

libraryDependencies += "com.github.alexarchambault" %% "argonaut-shapeless_6.1" % "0.3.1" 
*/ 

import java.time.LocalDate 

import argonaut._, Argonaut._ 
import doobie.imports._ 
import doobie.util.composite.Composite 
import tags._ 
import instances._ 
import org.http4s._ 

import scala.reflect.runtime.universe.TypeTag 
import scalaz._ 

object Main extends App { 
    implicitly[QueryParamDecoder[String @@ Social]] 
    println("ok") 
} 

object tags { 
    trait Social; val Social = Tag.of[Social] 
} 

object instances extends ExternalInstances1 { 
    implicit val ssn: InvariantInstances[String @@ Social] = 
    InvariantInstances { (raw: String) ⇒ 
     val digitsOnly = raw.filter(_.isDigit) 
     require(digitsOnly.length == 9) 
     Social(digitsOnly) 
    }(Social.unwrap) 
} 

trait ExternalInstances1 extends ExternalInstances0 { 
    implicit def iiCodecJson[B](implicit ii: InvariantInstances[B]): CodecJson[B] = ii.codecJson 
    implicit def iiEncodeJson[B](implicit ii: InvariantInstances[B]): EncodeJson[B] = ii.encodeJson 
    implicit def iiDecodeJson[B](implicit ii: InvariantInstances[B]): DecodeJson[B] = ii.decodeJson 
    implicit def iiMeta[B](implicit ii: InvariantInstances[B]): Meta[B] = ii.meta 
    implicit def iiQueryParamEncoder[B](implicit ii: InvariantInstances[B]): QueryParamEncoder[B] = ii.queryParamEncoder 
    implicit def iiQueryParamDecoder[B](implicit ii: InvariantInstances[B]): QueryParamDecoder[B] = ii.queryParamDecoder 
} 

trait ExternalInstances0 { 
    implicit def taggedEncodeJson[A, T](implicit A: EncodeJson[A]): EncodeJson[A @@ T] = 
    A.contramap(Tag.of[T].unwrap) 

    implicit def taggedDecodeJson[A, T](implicit A: DecodeJson[A]): DecodeJson[A @@ T] = 
    A.map(Tag.of[T].apply) 

    implicit def taggedComposite[A: Composite, T]: Composite[A @@ T] = 
    Composite[A].xmap(a => Tag[A, T](a), Tag.unwrap(_)) 

    implicit def taggedQueryParamDecoder[A: QueryParamDecoder, T]: QueryParamDecoder[A @@ T] = 
    QueryParamDecoder.decodeBy(Tag.of[T](_: A)) 

} 

trait InvariantInstances[B] { 
    def codecJson: CodecJson[B] 
    def decodeJson: DecodeJson[B] 
    def encodeJson: EncodeJson[B] 
    def meta: Meta[B] 
    def queryParamEncoder: QueryParamEncoder[B] 
    def queryParamDecoder: QueryParamDecoder[B] 
} 

object InvariantInstances { 
    def apply[A: EncodeJson: DecodeJson: Meta: QueryParamDecoder: QueryParamEncoder, B: TypeTag](f: A ⇒ B)(g: B ⇒ A) = 
    new InvariantInstances[B] { 
     def codecJson: CodecJson[B] = 
     CodecJson.derived[B](encodeJson, decodeJson) 

     def decodeJson: DecodeJson[B] = 
     implicitly[DecodeJson[A]].map(f) 

     def encodeJson: EncodeJson[B] = 
     implicitly[EncodeJson[A]].contramap(g) 

     def meta: Meta[B] = 
     implicitly[Meta[A]].xmap(f, g) 

     def queryParamDecoder: QueryParamDecoder[B] = 
     CovariantInstances.queryParamDecoder(f) 

     def queryParamEncoder: QueryParamEncoder[B] = 
     ContravariantInstances.queryParamEncoder(g) 
    } 
} 

object CovariantInstances { 
    def queryParamDecoder[A, B](f: A ⇒ B)(implicit A: QueryParamDecoder[A], 
             B: reflect.runtime.universe.TypeTag[B]): QueryParamDecoder[B] = 
    new QueryParamDecoder[B] { 
     import scalaz.Validation.FlatMap._ // suppress deprecation warning 

     def decode(value: QueryParameterValue): ValidationNel[ParseFailure, B] = 
     A.decode(value).flatMap(
      a ⇒ Validation.fromTryCatchNonFatal(f(a)).leftMap(t => 
      ParseFailure(s"Query decoding ${B.tpe.typeSymbol} failed", t.getMessage) 
     ).toValidationNel 
     ) 
    } 
} 

object ContravariantInstances { 
    def queryParamEncoder[A, B](g: B ⇒ A)(implicit A: QueryParamEncoder[A]): QueryParamEncoder[B] = 
    new QueryParamEncoder[B] { 
     def encode(value: B): QueryParameterValue = A.encode(g(value)) 
    } 
} 
+0

Non è possibile dare la priorità agli 'impliciti'. Attraverso l'ereditarietà puoi solo influenzare l'ordine di inizializzazione. Se si desidera l'implicito di 'ExternalInstances1' su * win * di quanto si dovrebbe sovrascrivere il 'implicits' di' ExternalInstances0'. –

+3

@Sascha Kolberg: Sì, è possibile, e questo è un idioma di scala comune. Vedere per esempio: http://stackoverflow.com/questions/1886953/is-there-a---control-which-implicit-conversion-will-be-the-default-used –

+1

Hm, my bad, didn lo so. Bene, dato che le tue due conversioni implicite hanno firme leggermente diverse, immagino che la conversione con priorità più bassa per eredità possa avere un punteggio migliore per la sua firma e, a causa di uno strano incidente, entrambi finiscono con lo stesso punteggio. Ma non vorrei essere la persona che esegue il debug di questo;) –

risposta

3

Basato sul commento di Sasha Kolberg e http://eed3si9n.com/revisiting-implicits-without-import-tax, Suppongo di avere una definizione "più specifica" in ExternalInstances0 (+1 punto) e una "priorità più alta "definizione in ExternalInstances1 (+1 punto) che porta a un pareggio e ambiguità.

La soluzione alternativa era aggiungere una definizione "più specifica, con priorità più alta" in ExternalInstances1 (+2 punti?) Per rompere il pareggio, anche se è la duplicazione del codice che era fondamentalmente il boilerplate per cominciare.

Mi piacerebbe conoscere una soluzione migliore. Grazie!

trait ExternalInstances1 extends ExternalInstances0 { 
    implicit def iiCodecJson[B](implicit ii: InvariantInstances[B]): CodecJson[B] = ii.codecJson 
    implicit def iiEncodeJson[B](implicit ii: InvariantInstances[B]): EncodeJson[B] = ii.encodeJson 
    implicit def iiDecodeJson[B](implicit ii: InvariantInstances[B]): DecodeJson[B] = ii.decodeJson 
    implicit def iiMeta[B](implicit ii: InvariantInstances[B]): Meta[B] = ii.meta 
    implicit def iiQueryParamEncoder[B](implicit ii: InvariantInstances[B]): QueryParamEncoder[B] = ii.queryParamEncoder 
    implicit def iiQueryParamDecoder[B](implicit ii: InvariantInstances[B]): QueryParamDecoder[B] = ii.queryParamDecoder 

    implicit def iiCodecJsonT[B,T](implicit ii: InvariantInstances[B @@ T]): CodecJson[B @@ T] = ii.codecJson 
    implicit def iiEncodeJsonT[B,T](implicit ii: InvariantInstances[B @@ T]): EncodeJson[B @@ T] = ii.encodeJson 
    implicit def iiDecodeJsonT[B,T](implicit ii: InvariantInstances[B @@ T]): DecodeJson[B @@ T] = ii.decodeJson 
    implicit def iiMetaT[B,T](implicit ii: InvariantInstances[B @@ T]): Meta[B @@ T] = ii.meta 
    implicit def iiQueryParamEncoderT[B,T](implicit ii: InvariantInstances[B @@ T]): QueryParamEncoder[B @@ T] = ii.queryParamEncoder 
    implicit def iiQueryParamDecoderT[B,T](implicit ii: InvariantInstances[B @@ T]): QueryParamDecoder[B @@ T] = ii.queryParamDecoder 
} 
+0

Non puoi semplicemente scambiare 'ExternalInstances0' e' ExternalInstances1'? Sembra funzionare bene (sia 'implicitamente [QueryParamDecoder [String @@ Social]] implicitamente [QueryParamDecoder [String]]' compila correttamente.Perché posso solo dire che compila bene, non che fa quello che ti aspetti. –

+0

Giusto, scambiandoli si risolverebbe il conflitto, ma dare 'ExternalInstances0' una priorità più alta, voglio che abbia una priorità più bassa. – arya