如何在功能提取摘要中添加类型检查

问题描述

我正在发现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] },如果输入的类型是静态的in this question,则该结构类型实际上试图静态获取字段的类型。 这是我第一次成功的尝试,下面是仍然存在和失败的地方。

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] {
[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方法的返回类型取决于输入类型。 有任何线索吗?

解决方法

好吧,多亏了这些评论,我得以精心设计以下答案,该答案不需要投影类型,但使用依赖类型,如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切换了新的缩进样式,我认为这很棒。