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

Scala 依赖方法类型有哪些引人注目的用例?,scala,haskell,programming-languages,type-systems,dependent-method-type,Scala,Haskell,Programming Languages,Type Systems,Dependent Method Type,依赖方法类型以前是一个实验性的特性,现在已经是了,显然这似乎是在Scala社区中创建的 乍一看,这对什么有用还不是很明显。Heiko Seeberger发布了一个依赖方法类型的简单示例,从注释中可以看出,可以很容易地使用方法上的类型参数来复制该示例。所以这不是一个很有说服力的例子。(我可能遗漏了一些明显的内容。如果是,请更正。) 依赖方法类型的用例有哪些实用且有用的例子,它们明显优于替代方法 我们可以用它们做哪些以前不可能/不容易的有趣的事情 相对于现有的类型系统功能,他们给我们买了什么 此外,

依赖方法类型以前是一个实验性的特性,现在已经是了,显然这似乎是在Scala社区中创建的

乍一看,这对什么有用还不是很明显。Heiko Seeberger发布了一个依赖方法类型的简单示例,从注释中可以看出,可以很容易地使用方法上的类型参数来复制该示例。所以这不是一个很有说服力的例子。(我可能遗漏了一些明显的内容。如果是,请更正。)

依赖方法类型的用例有哪些实用且有用的例子,它们明显优于替代方法

我们可以用它们做哪些以前不可能/不容易的有趣的事情

相对于现有的类型系统功能,他们给我们买了什么

此外,依赖方法类型是否与其他高级类型语言(如Haskell、OCaml)的类型系统中的任何特性类似或从中得到启发

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
,并且正在为它编写一个“pimp my library”扩展


关于第二个问题:通过此功能启用的类型远远弱于完全依赖类型(请参阅以了解其风格)。我认为我以前没有见过类似的情况。

或多或少,使用成员(即嵌套)类型会导致对依赖方法类型的需求。特别是,我认为没有依赖方法类型,经典的蛋糕模式更接近于反模式

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

我将用我在演讲中的一个练习来说明这一点

请注意,路径依赖性意味着编译器将保证
NetworkFileManager
上的
testHash
testDuplicates
方法只能通过与之对应的参数调用,即它自己的
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、Epigram和Agda这样的语言有一种不同形式的依赖类型,这种类型在某些方面更为通用,但与Scala不同的是,它是类型系统的一部分,没有子类型。

我支持声明式编程形式与环境状态的交互选择。这里的细节不相关(例如,关于回调的细节以及与Actor模型和序列化程序相结合的概念相似性)

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

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


但是,如果这些散列键是字符串或整数散列值,则散列映射元素类型的静态类型为Any或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
}
虽然我没有测试以下内容,但理论上我可以在运行时从类名中获取哈希键,因此哈希键是一个类名而不是字符串(使用Scala的倒勾在类名中嵌入字符串)


这一新特性在混凝土浇筑时需要。当使用类型参数时,类型依赖关系可以在最新和一些较旧版本的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, "")
         ^

是大卫·马西弗创造了这个词,但无论如何,它都是描述性的。这是一个奇妙的解释,解释了为什么依赖于方法类型
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
}
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
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, "")
         ^