3 votes

Aux Patterns pour les types de type supérieur

EDIT : Voici une formulation beaucoup plus simple du problème, en utilisant Foo comme un exemple de la Aux modèle qui fait travail :

// Foo is a simple Aux-pattern type
trait Foo[A, B] { type Out }

object Foo {
  type Aux[A, B, C] = Foo[A, B] { type Out = C }
  // One instance, turning Int+String into Boolean
  implicit val instance: Foo.Aux[Int, String, Boolean] = null
}

// Wrapper is exactly the same but contains a higher-kinded type
trait Wrapper[A, B] { type Contract[_] }

object Wrapper {
  type Aux[A, B, C[_]] = Wrapper[A, B] { type Contract[_] = C[_] }
  // One instance, linking Int + String to Option
  implicit val instance: Wrapper.Aux[Int, String, Option] = null
}

// Same test for both
def fooTest[A, B, C](implicit ev: Foo.Aux[A, B, C]): C = ???
def wrapperTest[X[_]](implicit ev: Wrapper.Aux[Int, String, X]): X[Boolean] = ???

// Compiles as expected
fooTest: Boolean

// Does not compile: could not find implicit value for parameter ev: Wrapper.Aux[Int,String,X]
wrapperTest: Option[Boolean]

// Does compile:
wrapperTest(implicitly[Wrapper.Aux[Int, String, Option]]): Option[Boolean]

Ancienne formulation de la question :

Toutes mes excuses pour l'exemple alambiqué ci-dessous. Je veux essentiellement dupliquer le Aux pour les types supérieurs.

Le scala :

// Foo is a normal Aux pattern calculation
trait Foo[A, B] { type Out }

object Foo {
  type Aux[A, B, C] = Foo[A, B] { type Out = C }
  // Foo turns Int + String into Boolean
  implicit val intInstance: Foo.Aux[Int, String, Boolean] = null
}

// Wrapper is supposed to be a type-level computation across
// type-level functions
// It takes two types and binds them with a contract (a nested
// type-level function)
trait Wrapper[A, B] { type Contract[X] }

object Wrapper {
  type Aux[A, B, C[_]] = Wrapper[A, B] { type Contract[X] = C[X] }

  // It has one instance: It binds Int and String to the type-level
  // function Foo.
  implicit val fooWrapper: Wrapper.Aux[Int, String, Foo.Aux[Int, String, ?]] = null

}

object Testing {

  trait TestResult[X]

  // We summon a Contr, which is provided by Wrapper
  // The idea is we get the result of Foo's computation without summoning
  // Foo explicitly. This allows us to easily swap Foo out for another
  // Function if we desire
  implicit def testing[A, B, Contr[_], X](
    implicit wrapper: Wrapper.Aux[A, B, Contr],
    foo: Contr[X]
  ): TestResult[X] = ???

  // Compiles as expected
  implicitly[Wrapper.Aux[Int, String, Foo.Aux[Int, String, ?]]]
  implicitly[Wrapper[Int, String]]
  implicitly[Foo.Aux[Int, String, Boolean]]
  implicitly[Foo[Int, String]]
  val result1: TestResult[Boolean] = testing[Int, String, Foo.Aux[Int, String, ?], Boolean]

  // Does not compile
  val result2: TestResult[Boolean] = testing
  implicitly[TestResult[Boolean]]
}

C'est ce que j'attends de cette dernière ligne :

  • Nous sommes à la recherche d'un TestResult[Boolean]
  • testing dit que nous avons besoin d'un Contr[Boolean] pour certains Contr fourni par Wrapper
  • Wrapper donne une seule instance de Contr[_] = Foo.Aux[Int, String, ?]
  • Donc le compilateur cherche un Foo.Aux[Int, String, Boolean]
  • Il existe une seule instance de ce type fournie par Foo
  • Donc le tout se compile

Voici mon build.sbt au cas où je manquerais quelque chose :

scalaVersion := "2.12.6"

scalacOptions := Seq(
  "-language:existentials",
  "-language:higherKinds",
  "-Ypartial-unification",  // EDIT
)

addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.8")

2voto

g.krastev Points 1013

Vous avez trouvé une solution intéressante mais le problème original est bien un bug : scala/bug#10849 qui a été corrigé dans Scala 2.13 : scala/scala#6573 . Malheureusement, il n'est pas possible de le rétablir dans la version 2.12 car il modifie la manière dont l'inférence de type fonctionne et il s'agit d'une partie délicate du compilateur qui n'est même pas spécifiée. Mais vous pouvez l'essayer avec Scala 2.13.0-M4 ou 2.13.0-M5.

1voto

Gesar Points 239

Voici une solution que j'ai trouvée :

trait Wrapper[A, B] { type Contract[_] }

object Wrapper {
  type Aux[A, B, C[_]] = Wrapper[A, B] { type Contract[_] = C[_] }
  // One instance, linking Int + String to Option
  implicit def instance[A, B](implicit ev1: A =:= Int, ev2: B =:= String): Wrapper.Aux[A, B, Option] = null

}

object Testing {

  def wrapperTest[A, B, X[_]](implicit ev: Wrapper.Aux[A, B, X]): X[Boolean] = ???

  // These compile now!!
  wrapperTest
  wrapperTest: Option[Boolean]

  // Do NOT compile, as expected
  // wrapperTest[Boolean, Char, Option]: Option[Boolean]
  // wrapperTest[Int, String, List]: Option[Boolean]

}

Je ne sais pas. pourquoi cela fonctionne précisément, mais il semble que la liberté de A y B permettent au compilateur de se concentrer sur la résolution de X[_] correctement, et ensuite les contraintes sur A y B se produisent à un niveau différent, de sorte que nous obtenons la même fonctionnalité à la fin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X