如何将类型检查添加到函数提取摘要

How to add type-checking to a function extraction summary

我正在发现 Dotty,我很想提出我的算法的类型化版本。 我想实现以下我可以在 JavaScript 中轻松完成的目标。它基本上是提取记录或数组的 属性 的一种浓缩方式:

function Field(key, action) {
  return {
    apply(record) {
      return action.apply(record[key]);
    }
  };
}
var Identity = { apply(record) { return record; } };

console.log(Field(3, Field("a", Identity)).apply([0, 1, 2, {a: "Hello"}]))
// Prints out "Hello"

我有一堆函数,比如我正在尝试输入的 Field。 到目前为止,这是我尝试过的。记录或对象被建模为结构类型{ def get(k: Key): KeyMapper[Key] },如果输入的类型是静态已知的,它基本上试图静态获取字段的类型,如。 这是我第一次成功的尝试,下面是剩下的和失败的。

trait Apply[A, B] {
  def apply(a: A): B
}
case class Identity[A]() extends Apply[A, A] {
  def apply(a: A) = a
}

case class Field
  [Key: ClassTag,
   KeyMapper[_],
   Record <: { def get(k: Key): KeyMapper[Key]},
   B](key: Key, subAction: Apply[KeyMapper[Key], B]) extends Apply[Record, B] {
    def apply(record: Record) = subAction(record.get(key))
}

到目前为止一切顺利,编译时没有出现类型错误。现在,我希望将类型定义 KeyKeyMapper 集成为记录的一部分,这样我只有两个类型参数,而不是四个,并且代码更易于维护。 我尝试了以下方法:

trait Record {
  type KeyMapper[T]
  type Key
  def apply(k: Key): KeyMapper[Key]
}
case class Field[A <: Record, U](key: A#Key, subAction: Apply[A#KeyMapper[A#Key], U]) extends Apply[A, U] {
    def apply(record: A): U = subAction(record(key))

我收到以下错误::

[error]    |    def apply(record: A): U = subAction(record(key))
[error]    |                                               ^^^
[error]    |                                       Found:    (Down.this.key : A#Key)
[error]    |                                       Required: record.Key

好的,到目前为止,除了将 key 转换为 .asInstanceOf[record.Key] 之外,我没有看到任何其他方法,然后我收到以下错误:

[error] 43 |    def apply(record: A): U = subAction(record(key.asInstanceOf[record.Key]))
[error]    |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |                                  Found:    record.KeyMapper[record.Key]
[error]    |                                  Required: A#KeyMapper[A#Key]

好吧,我有点失望,但我给 A#KeyMapper[A#Key] 添加了一个演员表。然后我得到错误:

[error] 42 |  case class Field[A <: Record, U](key: A#Key, subAction: Apply[A#KeyMapper[A#Key], U]) extends Apply[A, U] {
[error]    |                                        ^
[error]    |                                        A is not a legal path
[error]    |                                        since it is not a concrete type

嗯,所以我读了一下,发现类型投影已被弃用并从 Dotty 中删除,所以我需要有一个具体的值。这是我的下一次尝试:

trait RecordAndEdit { outer =>
  type Val <: {
    def get(k: outer.Key): outer.Map[k.type]
  }
  type Key
  type Map[_]
}
class Field[U](val rOps: RecordAndEdit)(val key: rOps.Key, val subAction: Apply[rOps.Map[rOps.Key], U]) extends Apply[rOps.Val, U] {
  def apply(record: rOps.Val): U = subAction(record.get(key))
}

我收到错误

[error] 35 |    def apply(record: rOps.Val): U = subAction(record.get(key))
[error]    |                                               ^^^^^^^^^^^^^^^
[error]    |Structural access not allowed on method get because it has a method type with inter-parameter dependencies

此时,我不明白如何解决此错误消息,因为我希望 get 方法具有取决于输入类型的 return 类型。 有什么线索吗?

好的,多亏了评论,我才得以精心设计以下答案,不需要投影类型,但使用依赖类型,如 this answer:

  trait Apply[Input, Output]:
    def apply(k: Input): Output
  
  trait WithDomain[X] {
    type Key
    type KeyMapper[_ <: Key]
    def get(x: X, k: Key): KeyMapper[k.type]
  }
  
  class Field[Input, Output](using val g: WithDomain[Input])(val key: g.Key, val next: RecordEdit[g.KeyMapper[key.type], Output]) extends Apply[Input, Output]:
    def apply(r: Input): Output =
      next(g.get(r, key))
      
  object Field:
    def apply[Input, Output](using g: WithDomain[Input])(key: g.Key, next: RecordEdit[g.KeyMapper[key.type], Output]): RecordEdit[Input, Output] = 
      new Field[Input, Output]()(key, next)
   
  class Identity[T] extends RecordEdit[T, T]:
    def apply(r: T) = r
  
  object Identity:
    def apply[T]() = new Identity[T]()

一切都按预期进行,例如:

  class Node(val tag: String, val children: Array[Node] = Array())

  given as WithDomain[Node] { self =>
    type Key = "tag" | "children"
    type Mapping[X <: self.Key] = (X match {
      case "tag" => String
      case "children" => Array[Node]
    })
    def get(x: Node, k: self.Key): self.Mapping[k.type] = k match {
      case _: "tag" => x.tag
      case _: "children" => x.children
    }
  }
  
  given[T] as WithDomain[Array[T]] {
    type Key = Int
    type Mapping[Int] = T
    def get(x: Array[T], k: Int): T = x(k)
  }

  println(Field[Node, String]("children",
            Field[Array[Node], String](0,
               Field[Node, String]("tag", Identity())))(
           Node("hello world", Array(Node("hi world")))))

请注意,我还为 Dotty 切换到了新的 indentation-style,我认为这很棒。