问题描述
我正在发现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))
}
到目前为止,它编译时没有类型错误。现在,我希望将类型定义Key
和KeyMapper
集成为记录的一部分,以便我只有两个类型参数,而不是四个,并且代码更易于维护。
我尝试了以下方法:
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切换了新的缩进样式,我认为这很棒。