Aux Pattern for higher-kinded types

筅森魡賤 提交于 2019-12-21 20:08:39

问题


EDIT: Here is a much simpler formulation of the problem, using Foo as an example of the Aux pattern which does work:

// 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]

Old formulation of the question:

Apologies for the convoluted example below. I essentially want to duplicate the Aux pattern for higher-kinded types.

The 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]]
}

This is what I expect to happen in that last line:

  • We're searching for a TestResult[Boolean]
  • testing says we need a Contr[Boolean] for some Contr provided by Wrapper
  • Wrapper gives a single instance of Contr[_] = Foo.Aux[Int, String, ?]
  • So compiler is searching for a Foo.Aux[Int, String, Boolean]
  • There is a single such instance provided by Foo
  • So the whole thing compiles

Here is my build.sbt in case I am missing something:

scalaVersion := "2.12.6"

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

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

回答1:


You came up with an interesting workaround but the original problem is indeed a bug: scala/bug#10849, which was fixed in Scala 2.13: scala/scala#6573. Unfortunately it cannot be backported to 2.12 because it changes the way type inference works and this is a delicate part of the compiler which is not even specced. But you can try it out with Scala 2.13.0-M4 or 2.13.0-M5.




回答2:


Here is a solution I came up with:

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]

}

I don't know why it works precisely, but it seems like the freedom of A and B allow the compiler to focus on resolving X[_] properly, and then the constraints on A and B happen at a different level so we achieve the same functionality in the end.



来源:https://stackoverflow.com/questions/52581986/aux-pattern-for-higher-kinded-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!