【问题标题】:Cannot prove equivalence with a path dependent type无法证明与路径相关类型的等价性
【发布时间】:2021-09-09 15:20:08
【问题描述】:

为什么最后一个summon 编译失败?我该怎么做才能让它编译?

import java.time.{LocalDateTime, LocalTime}

trait Circular[T]:
  type Parent

given localTimeCircular: Circular[LocalTime] with
  type Parent = LocalDateTime

final class CircularMap[K, +V]()(using val circular: Circular[K])

val summoned = summon[Circular[LocalTime]]
val daily = new CircularMap[LocalTime, Int]()
println(summoned == daily.circular) // Prints true

summon[localTimeCircular.Parent =:= LocalDateTime]
summon[summoned.Parent =:= LocalDateTime]

summon[daily.circular.Parent =:= LocalDateTime]

Scastie link

在 Scala 3.0.2 和 3.1.0-RC1 上失败

【问题讨论】:

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


    【解决方案1】:

    这件事不在 Scala 3 中。在 Scala 2.13.6 中,类似代码无法编译

    import shapeless.the
    
    import java.time.{LocalDateTime, LocalTime}
    
    trait Circular[T] {
      type Parent
    }
    
    implicit object localTimeCircular extends Circular[LocalTime] {
      type Parent = LocalDateTime
    }
    //  implicit val localTimeCircular: Circular[LocalTime] {type Parent = LocalDateTime} = new Circular[LocalTime] {
    //    type Parent = LocalDateTime
    //  }
    
    final class CircularMap[K, +V]()(implicit val circular: Circular[K])
    
    val summoned = the[Circular[LocalTime]]
    val daily = new CircularMap[LocalTime, Int]()
    println(summoned == daily.circular) // Prints true
    
    implicitly[localTimeCircular.Parent =:= LocalDateTime]
    implicitly[summoned.Parent =:= LocalDateTime]
    
    //implicitly[daily.circular.Parent =:= LocalDateTime] // doesn't compile
    

    https://scastie.scala-lang.org/DmytroMitin/lsX3fS3ET0ajzChwl2Mctw

    (我将implicitly 替换为the 在一处,因为implicitly 会破坏类型细化,summon 更像the)。

    让我们简化您的代码,以便弄清楚发生了什么。让我们移除隐含(让我们明确地解决它们)

    import java.time.{LocalDateTime, LocalTime}
    
    trait Circular[T] {
      type Parent
    }
    
    val localTimeCircular = new Circular[LocalTime] {
      type Parent = LocalDateTime
    }
    
    final class CircularMap[K, +V](val circular: Circular[K])
    
    val summoned = localTimeCircular
    val daily = new CircularMap[LocalTime, Int](localTimeCircular)
    println(summoned == daily.circular) // Prints true
    
    implicitly[localTimeCircular.Parent =:= LocalDateTime]
    implicitly[summoned.Parent =:= LocalDateTime]
    
    //implicitly[daily.circular.Parent =:= LocalDateTime] // doesn't compile
    

    问题在于路径相关类型的设计使得即使x == x1 也不需要x.T =:= x1.T

    In the latest release of scala (2.12.x), is the implementation of path-dependent type incomplete?

    Force dependent types resolution for implicit calls

    How to create an instances for typeclass with dependent type using shapeless

    summoned == daily.circular 是真的,但 summoned 的类型为 Circular[LocalTime] { type Parent = LocalDateTime },而 daily.circular 的类型为 Circular[LocalTime]。您这样指定:val circular: Circular[K]。因此,您将精炼类型Circular[LocalTime] { type Parent = LocalDateTime }(我们将其表示为Circular.Aux[LocalTime, LocalDateTime])向上转换为其超类型,即没有精炼的类型Circular[LocalTime](又名存在类型Circular.Aux[LocalTime, _])。所以类型 localTimeCircular.Parentsummoned.ParentLocalDateTime 但类型 daily.circular.Parent 是抽象的。例如,如果你想恢复类型细化,你可以定义

    final class CircularMap[K, +V, P](val circular: Circular[K] {type Parent = P})
    

    另一种方法是使用单例类型

    final class CircularMap[K, +V](val circular: localTimeCircular.type)
    

    有没有在不引入类型参数P的情况下不丢失细化Circular[LocalTime] { type Parent = LocalDateTime }的方法 班级CircularMap?就像是 final class CircularMap[K, +V](val circular: Circular[K] { type Parent = X }) 其中X 将在CircularMap 主体的范围内引入一个新的类型变量。

    您想要的实际上是类上的多个类型参数列表(以便在class CircularMap[K, +V][P] 中您可以指定KV 并推断P

    https://github.com/scala/bug/issues/4719

    https://contributors.scala-lang.org/t/multiple-type-parameter-lists-in-dotty-si-4719/2399

    你可以模仿他们

    import java.time.{LocalDateTime, LocalTime}
    
    trait Circular[T] {
      type Parent
    }
    
    val localTimeCircular = new Circular[LocalTime] {
      type Parent = LocalDateTime
    }
    
    trait CircularMap[K, +V] {
      type P
      val circular: Circular[K] {type Parent = P}
    }
    object CircularMap {
      def apply[K, V]: PartiallyApplied[K, V] = new PartiallyApplied[K, V]
    
      class PartiallyApplied[K, V] {
        def apply[_P](_circular: Circular[K] {type Parent = _P}): CircularMap[K, V] {type P = _P} = new CircularMap[K, V] {
          override type P = _P
          override val circular: Circular[K] {type Parent = _P} = _circular
        }
      }
    }
    
    val summoned = localTimeCircular
    val daily = CircularMap[LocalTime, Int](localTimeCircular)
    println(summoned == daily.circular) // Prints true
    
    implicitly[localTimeCircular.Parent =:= LocalDateTime]
    implicitly[summoned.Parent =:= LocalDateTime]
    implicitly[daily.circular.Parent =:= LocalDateTime] // compiles
    

    【讨论】:

    • CircularMap类上不引入类型参数P,有没有办法不丢失细化Circular[LocalTime] { type Parent = LocalDateTime }
    • final class CircularMap[K, +V](val circular: Circular[K] { type Parent = X }) 这样X 会在CircularMap 主体的范围内引入一个新的类型变量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多