依赖方法类型有哪些引人注目的用例?

依赖方法类型(以前曾经是一个实验特性)现在已经是 在主干中默认启用了,而且很明显,这似乎已经在 Scala 社区中创建了 一些刺激

乍看之下,还不能立即看出它有什么用处。Heiko Seeberger 发布了一个依赖方法类型 给你的简单示例,从注释中可以看出,可以很容易地用方法上的类型参数复制它。所以这不是一个很有说服力的例子。(我可能遗漏了一些显而易见的东西。如果有,请纠正我。)

在依赖方法类型的用例中,有哪些实际和有用的例子明显优于替代方法?

我们能用它们做些什么以前不可能/不容易的有趣的事情呢?

他们给我们买了什么现有的类型系统功能?

此外,依赖方法类型是否类似于其他高级类型语言(如 Haskell、 OCaml)的类型系统中的任何特性,或者从中获得灵感?

12942 次浏览
trait Graph {
type Node
type Edge
def end1(e: Edge): Node
def end2(e: Edge): Node
def nodes: Set[Node]
def edges: Set[Edge]
}

在其他地方,我们可以静态地保证不会将两个不同图中的节点混合在一起,例如:

def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ...

当然,如果在 Graph内部定义,这已经起作用了,但是假设我们不能修改 Graph,并且正在为它编写一个“ pip my library”扩展。

关于第二个问题: 这个特性启用的类型比完全依赖类型的 很远要弱(参见 Agda 的依赖类型编程了解这方面的内容)我以前从没见过这样的类比。

或多或少地使用成员(即。嵌套)类型可能引起对依赖方法类型的需求。特别是,我坚持认为,如果没有依赖方法类型,经典的 cake 模式更接近于反模式。

有什么问题吗?Scala 中的嵌套类型依赖于它们的封闭实例。因此,在没有依赖方法类型的情况下,尝试在该实例之外使用它们可能会非常困难。这可以把最初看起来优雅和吸引人的设计变成可怕的僵硬和难以重构的怪物。

我将用我在 高级 Scala 培训课程课上做的一个练习来说明这一点,

trait ResourceManager {
type Resource <: BasicResource
trait BasicResource {
def hash : String
def duplicates(r : Resource) : Boolean
}
def create : Resource


// Test methods: exercise is to move them outside ResourceManager
def testHash(r : Resource) = assert(r.hash == "9e47088d")
def testDuplicates(r : Resource) = assert(r.duplicates(r))
}


trait FileManager extends ResourceManager {
type Resource <: File
trait File extends BasicResource {
def local : Boolean
}
override def create : Resource
}


class NetworkFileManager extends FileManager {
type Resource = RemoteFile
class RemoteFile extends File {
def local = false
def hash = "9e47088d"
def duplicates(r : Resource) = (local == r.local) && (hash == r.hash)
}
override def create : Resource = new RemoteFile
}

这是典型的蛋糕模式的一个例子: 我们有一系列的抽象,它们通过一个层次结构逐渐被提炼(ResourceManager/ResourceFileManager/File提炼,NetworkFileManager/RemoteFile继而提炼)。这是一个玩具示例,但是模式是真实的: 它在 Scala 编译器中被广泛使用,在 Scala Eclipse 插件中也被广泛使用。

这里有一个使用中的抽象的例子,

val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)

注意,路径依赖性意味着编译器将保证 NetworkFileManager上的 testHashtestDuplicates方法只能使用与之对应的参数调用,即。它是自己的 RemoteFiles,没有别的。

不可否认,这是一个令人满意的属性,但是假设我们想要将这个测试代码移动到一个不同的源文件中呢?对于依赖方法类型,很容易在 ResourceManager层次结构之外重新定义这些方法,

def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")


def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))

注意这里依赖方法类型的使用: 第二个参数(rm.Resource)的类型取决于第一个参数(rm)的值。

在没有依赖方法类型的情况下也可以做到这一点,但是这样做非常笨拙,而且机制也非常不直观: 我已经教这门课近两年了,在这段时间里,没有人能够自发地提出一个可行的解决方案。

你自己试试..。

// Reimplement the testHash and testDuplicates methods outside
// the ResourceManager hierarchy without using dependent method types
def testHash        // TODO ...
def testDuplicates  // TODO ...


testHash(rf)
testDuplicates(rf)

经过一段时间的斗争,你可能会发现为什么我(或者可能是大卫麦克维尔,我们不记得我们谁发明了这个术语)称之为末日面包房。

编辑: 强烈的共识是,《末日面包店》是大卫 · 麦克伊弗的发明..。

额外的好处是: Scala 的依赖类型形式(以及作为其一部分的依赖方法类型)受到了编程语言 贝塔的启发... ... 它们自然而然地产生于 Beta 的一致嵌套语义。我不知道任何其他的甚至隐约主流的编程语言有这种形式的依赖类型。像 Coq、 Cayenne、 Eigram 和 Agda 这样的语言有一种不同形式的依赖类型,在某些方面更加通用,但是由于它们是类型系统的一部分,所以有很大的不同,与 Scala 不同的是,它们没有子类型。

我是美国广播公司宣告式编程代表,为环境保护提供咨询。这里的细节是不相关的(例如关于回调的细节和与 Actor 模型和 Serializer 组合的概念相似性)。

相关的问题是,状态值存储在哈希映射中,并由哈希键值引用。函数输入的不可变参数是来自环境的值,可以调用其他这样的函数,并将状态写入环境。但是函数是来自环境的 不允许阅读值(因此函数的内部代码不依赖于状态更改的顺序,因此在这个意义上仍然是声明性的)。如何在 Scala 中输入这个?

环境类必须有一个重载的方法,该方法输入要调用的函数,并输入函数参数的散列键。因此,此方法可以使用散列映射中的必要值调用函数,而不提供对这些值的公共读访问(因此根据需要,拒绝函数从环境中读取值的能力)。

但是如果这些散列键是字符串或整数散列值,那么散列映射元素类型 包括到 AnyRef (散列映射代码未在下面显示)的静态类型,因此可能会发生运行时不匹配,也就是说,对于给定的散列键,可以在散列映射中放置任何类型的值。

trait Env {
...
def callit[A](func: Env => Any => A, arg1key: String): A
def callit[A](func: Env => Any => Any => A, arg1key: String, arg2key: String): A
}

虽然我没有测试以下内容,但理论上我可以在运行时使用 classOf从类名中获得散列键,所以散列键是类名而不是字符串(使用 Scala 的反勾将字符串嵌入到类名中)。

trait DependentHashKey {
type ValueType
}
trait `the hash key string` extends DependentHashKey {
type ValueType <: SomeType
}

从而实现了静态类型安全。

def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A

混凝土 使用抽象类型成员代替类型参数时需要这个新特性。当使用类型参数时,家族多态性类型依赖关系可以在 Scala 的最新版本和一些旧版本中表示,如下面的简化示例所示。

trait C[A]
def f[M](a: C[M], b: M) = b
class C1 extends C[Int]
class C2 extends C[String]


f(new C1, 0)
res0: Int = 0
f(new C2, "")
res1: java.lang.String =
f(new C1, "")
error: type mismatch;
found   : C1
required: C[Any]
f(new C1, "")
^