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

时间:2011-10-22 14:16:25

标签: scala haskell programming-languages type-systems dependent-method-type

依赖方法类型,以前曾是一个实验性功能,现在已经enabled by default in the trunk,显然这似乎在Scala社区中创建了some excitement

初看起来,这并不是显而易见的。 Heiko Seeberger发布了一个依赖方法类型here的简单示例,可以在注释中看到,可以使用方法上的类型参数轻松复制。所以这不是一个非常引人注目的例子。 (我可能会遗漏一些明显的东西。如果是这样,请纠正我。)

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

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

他们通过现有的类型系统功能向我们购买了什么?

此外,依赖方法类型是否类似于或从其他高级类型语言(如Haskell,OCaml)的类型系统中找到的任何功能中汲取灵感?

4 个答案:

答案 0 :(得分:111)

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

那么问题是什么? Scala中的嵌套类型取决于它们的封闭实例。因此,在没有依赖方法类型的情况下,尝试在该实例之外使用它们会令人沮丧地困难。这可以将最初看起来优雅且吸引人的设计转变为怪异的怪物,这些怪物是苛刻的,难以重构。

我将通过我在Advanced Scala training course

期间进行的练习来说明这一点
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 / {{1}精炼然后由File / NetworkFileManager精炼而成。这是一个玩具示例,但模式是真实的:它在整个Scala编译器中使用,并在Scala Eclipse插件中广泛使用。

这是使用抽象的一个例子,

RemoteFile

请注意,路径依赖性意味着编译器将保证val nfm = new NetworkFileManager val rf : nfm.Resource = nfm.create nfm.testHash(rf) nfm.testDuplicates(rf) 上的testHashtestDuplicates方法只能使用与其对应的参数调用,即。它是自己的NetworkFileManager,没有别的。

这无疑是一个理想的属性,但是假设我们想将这个测试代码移到另一个源文件中?使用依赖方法类型,在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

经过一段时间的努力,你可能会发现为什么我(或者也许是David MacIver,我们不记得我们中的哪个人创造了这个词)称之为“毁灭面包店”。

编辑:一致认为面包店的毁灭是David MacIver的造币......

对于奖励:Scala的一般依赖类型(以及作为其一部分的依赖方法类型)受到编程语言Beta的启发......它们自然地来自Beta的一致嵌套语义。我不知道任何其他甚至微弱的主流编程语言,它具有这种形式的依赖类型。像Coq,Cayenne,Epigram和Agda这样的语言有一种不同形式的依赖类型,这在某些方面更为通用,但作为类型系统的一部分,与Scala不同,它没有显着不同。

答案 1 :(得分:52)

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”扩展名。

关于第二个问题:此功能启用的类型弱于完全依赖类型(请参阅Dependently Typed Programming in Agda了解其中的风格。)我不认为我见过以前的比喻。

答案 2 :(得分:6)

具体 abstract type members are used instead of type parameters时需要此新功能。使用类型参数时,family polymorphism类型依赖关系可以在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, "")
         ^

答案 3 :(得分:3)

我是developing a model,用于与环境状态的一种形式的声明性编程的交互。这里的细节不相关(例如关于回调和与Actor模型结合使用的序列化程序的概念相似性的详细信息)。

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

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

但是如果这些哈希键是字符串或整数哈希值,则哈希映射元素的静态类型类型为subsumes到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
}

虽然我没有测试以下内容,但理论上我可以在运行时使用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