【问题标题】:How to help the Scala 3 compiler infer a path-dependent-type?如何帮助 Scala 3 编译器推断路径相关类型?
【发布时间】:2021-11-17 15:52:09
【问题描述】:

假设,只要类型 A 可以表示一个不太精确的物理值测量,我就会有一个 Imprecise[A] 的实例。

trait Imprecise[A]:
  type Precision
  val ord: Ordering[Precision]
  def precision(a: A): Precision

例如,这个Bounds 类型在它的伴生对象中有一个给定的实例:

case class Bounds[N: Numeric](min: N, max: N):
  if summon[Ordering[N]].compare(min, max) > 1 then
    throw new IllegalArgumentException

object Bounds:
  import scala.math.Numeric.Implicits.infixNumericOps
  import scala.math.Ordering.Implicits.infixOrderingOps
  given [N: Numeric]: Imprecise[Bounds[N]] with
    type Precision = N
    val ord = summon[Ordering[N]]
    def precision(rng: Bounds[N]) = rng.max - rng.min

现在我的程序可以开始处理需要观察的物理特征了:

trait Feature[A:  Imprecise]:
  type Observation = A
  val imprecise = summon[Imprecise[A]]
  type Precision = imprecise.Precision

  def within(bound: Precision) = new RequiredFeature(this, bound)

class RequiredFeature(val feature: Feature[_], val min_precision: feature.Precision)


case class Temperature(chamber_id: Int) extends Feature[Bounds[Double]]
case class Pressure(chamber_id: Int) extends Feature[Bounds[Float]]

但是,当我最终尝试制作所需的功能时:

val rf = Temperature(3).within(7.0)

编译器抱怨

Found:    (7.0d : Double)
Required: ?1.Precision

where:    ?1 is an unknown value of type exp.Temperature

val rf = Temperature(3).within(7.0)

如果我放弃任何依赖于路径的类型并将精度作为常规泛型类型参数到处传递,那么到目前为止编写的代码可以编译。但是如果我不想这样做呢?是否有类似 Aux 模式的策略可以帮助编译器看到这是对 within 的有效调用?

【问题讨论】:

  • 应该summon[Ordering[N]].compare(min, max) > 1= 1 吗? (或> 0?)
  • class RequiredFeature 没有泛型,但您在 def within(bound: Precision) = new RequiredFeature[A](this, bound) 中使用它就好像它有一样。
  • @DmytroMitin 这可能是我在将代码复制到 Stackoverflow 时错误地调整了代码。我将对其进行编辑以确保唯一的错误是我要询问的那个。
  • @DmytroMitin 我认为this 显示了问题的根源,由于某种原因,编译器不知道 Precision 的正确类型,不确定是否需要这样做,或者是错误还是错误在代码中。
  • @DmytroMitin 是的compare(min, max) > 1 应该是compare(min, max) > 0,但这与此处的类型推断无关。

标签: scala type-inference scala-3 path-dependent-type


【解决方案1】:

这个问题类似于Cannot prove equivalence with a path dependent type

事物不是隐含的,事物是依赖于路径的类型。最简单的例子是

trait A:
  type T

val a0 = new A:
  type T = Int

summon[a0.T =:= Int]

class B(val a: A) // a has type A, not A { type T = ... }

val b = B(a0)

// summon[b.a.T =:= Int] // doesn't compile

为了进行编译,我们可以使用单例类型 class B(val a: a0.type) 或添加类型参数 class B[_T](val a: A { type T = _T })

trait Feature[A: Imprecise] 被脱糖成 trait Feature[A](using ev: Imprecise[A]),在这里你失去了类型改进,将 Imprecise[A] { type Precision = ... } 向上转换为 Imprecise[A]。在Same type ascription loses type member information on variable declaration but not on method parameter declarationusing ev: Imprecise[A] 中讨论了变量与方法参数中路径相关类型的行为差异,trait Feature[A](using ev: Imprecise[A]) 声明了一个变量。

所以你应该恢复Imprecise的类型细化,并为Feature添加一个类型参数

trait Imprecise[A]:
  type Precision
  val ord: Ordering[Precision]
  def precision(a: A): Precision

object Imprecise:
  type Aux[A, P] = Imprecise[A] with
    type Precision = P

case class Bounds[N: Numeric](min: N, max: N):
  if summon[Ordering[N]].compare(min, max) > 0 then
    throw new IllegalArgumentException

object Bounds:
  import Numeric.Implicits._
  given [N: Numeric]: Imprecise[Bounds[N]] with
    type Precision = N
    val ord = summon[Ordering[N]]
    def precision(rng: Bounds[N]) = rng.max - rng.min

trait Feature[A, P](using Imprecise.Aux[A, P]):
  type Observation = A
  val imprecise = summon[Imprecise[A]]
  type Precision = imprecise.Precision

  def within(bound: Precision) = new RequiredFeature(this, bound)

class RequiredFeature(val feature: Feature[_,_], val min_precision: feature.Precision)

case class Temperature(chamber_id: Int) extends Feature[Bounds[Double], Double]
case class Pressure(chamber_id: Int) extends Feature[Bounds[Float], Float]

val rf = Temperature(3).within(7.0)

或将类型成员添加到Feature

trait Imprecise[A]:
  type Precision
  val ord: Ordering[Precision]
  def precision(a: A): Precision

object Imprecise:
  type Aux[A, P] = Imprecise[A] with
    type Precision = P

case class Bounds[N: Numeric](min: N, max: N):
  if summon[Ordering[N]].compare(min, max) > 0 then
    throw new IllegalArgumentException

object Bounds:
  import Numeric.Implicits._
  given [N: Numeric]: Imprecise[Bounds[N]] with
    type Precision = N
    val ord = summon[Ordering[N]]
    def precision(rng: Bounds[N]) = rng.max - rng.min

trait Feature[A]:
  type P
  val ev: Imprecise.Aux[A, P]
  given Imprecise.Aux[A, P] = ev

  type Observation = A
  val imprecise = summon[Imprecise[A]] // ev
  type Precision = imprecise.Precision // P

  def within(bound: Precision) = new RequiredFeature(this, bound)

class RequiredFeature(val feature: Feature[_], val min_precision: feature.Precision)

case class Temperature[_P](chamber_id: Int)(using _ev: Imprecise.Aux[Bounds[Double], _P]) extends Feature[Bounds[Double]]:
  type P = _P
  val ev = _ev
case class Pressure[_P](chamber_id: Int)(using _ev: Imprecise.Aux[Bounds[Float], _P]) extends Feature[Bounds[Float]]:
  type P = _P
  val ev = _ev

val rf = Temperature(3).within(7.0)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多