什么是一些引人注目的依赖方法types的用例?

依赖的方法types,以前曾经是一个实验性的function,现在已经在trunk中被默认启用 ,显然这似乎在Scala社区中引起了一些兴奋 。

第一次看后,这可能是有用的。 Heiko Seeberger在这里发表了一个简单的依赖方法types的例子,在注释中可以很容易地用方法的types参数来重现。 所以这不是一个非常有说服力的例子。 (我可能会漏掉一些明显的东西,如果是这样,请纠正我。

什么是依赖方法types的用例的实用和有用的例子,它们明显比替代方法有优势? 我们能做些什么有趣的事情,而这些事情以前是不可能的? 他们通过现有的types系统function向我们购买什么?

谢谢!


红利问题:依赖的方法types是否与其他高级types语言的types系统(如Haskell,OCaml)中的任何特性类似/从中吸取灵感?

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

所以有什么问题? Scala中的嵌套types取决于它们的封装实例。 因此,在不存在依赖方法types的情况下,尝试在该实例之外使用它们可能是非常困难的。 这可以把最初看起来优雅和有吸引力的devise变成噩梦般僵硬而难以重构的怪物。

我将通过在高级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进行细化,然后由RemoteFile / RemoteFile )逐步细化。 这是一个玩具的例子,但是模式是真实的:它在整个Scala编译器中使用,并广泛用于Scala Eclipse插件。

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

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

请注意,path依赖性意味着编译器将保证testDuplicates上的testHashtestDuplicates方法只能使用与它相对应的参数来调用。 它是自己的RemoteFiles ,没有别的。

这无疑是一个理想的属性,但是假设我们想把这个testing代码移到不同的源文件中? 使用依赖方法types可以轻松地在ResourceManager层次结构外重新定义这些方法,

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

请注意依赖方法types的用法:第二个参数( rm.Resource )的types取决于第一个参数( rm )的值。

在没有依赖方法的情况下做到这一点是可能的,但是这是非常尴尬的,而且这个机制是非常不直观的:我已经教这个课程将近两年了,在那个时候,没有人提出一个自发的解决scheme。

为自己尝试…

 // 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) 

经过一段时间的苦苦挣扎之后,你可能会发现为什么我(或者也许是David MacIver,我们不记得我们是谁创造了这个术语)称之为厄运面包(Bakery of Doom)。

编辑:共识是面包店的末日是大卫麦克维尔的硬币…

对于奖金:斯卡拉一般依赖types的forms(和依赖方法types作为它的一部分)受到编程语言Beta的启发…他们自然产生于Beta的一致嵌套语义。 我不知道任何其他甚至微弱的主stream编程语言,这种forms依赖types。 像Coq,Cayenne,Epigram和Agda这样的语言有不同的依赖打字forms,这在某种程度上是比较普遍的,但是与Scala不同的是,它们是types系统的一部分。

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

在其他地方我们可以静态地保证我们不会混合来自两个不同graphics的节点,例如:

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

当然,如果在Graph定义,这已经工作了,但是说我们不能修改Graph并正在为它写一个“pimp my library”扩展。

关于第二个问题:由这个特性启用的types比完全的依赖types弱得多(参见Agda中的依赖types编程 。)我不认为我以前曾经见过类比。

当使用具体 抽象types成员而不是types参数时,这个新特性是需要的。 使用types参数时, 家族多态性types依赖关系可以在最新的和一些旧版本的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, "") ^ 

我正在开发一个与环境状态的声明性编程forms的交互模型 。 细节在这里是不相关的(例如,关于callback的细节和Actor模型与Serializer相结合的概念相似性)。

相关的问题是状态值被存储在散列映射中,并被散列键值引用。 函数input来自环境的值的不可变参数,可以调用其他这样的函数,并向环境写入状态。 但函数不允许从环境中读取值(所以函数的内部代码不依赖于状态变化的顺序,因此在这个意义上仍然是声明的)。 如何在Scala中input这个?

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

但是,如果这些散列键是string或整数散列值,那么散列映射元素types的静态types包含到Any或AnyRef(散列映射代码未在下面示出),并且因此可能发生运行时间不匹配,即,将是可能的在给定的散列键的哈希映射中放置任何types的值。

 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 } 

虽然我没有testing以下内容,理论上我可以在运行时使用classOf从类名获得哈希键,所以哈希键是类名而不是string(使用Scala的反引号将stringembedded到类名中) 。

 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