scala 依赖方法类型有哪些引人注目的用例?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7860163/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are some compelling use cases for dependent method types?
提问by missingfaktor
Dependent method types, which used to be an experimental feature before, has now been enabled by default in the trunk, and apparently this seems to have created some excitementin the Scala community.
依赖方法类型,以前曾经是一个实验性的特性,现在在主干中默认启用,显然这似乎在 Scala 社区中引起了一些兴奋。
At first look, it's not immediately obvious what this could be useful for. Heiko Seeberger posted a simple example of dependent method types here, which as can be seen in the comment there can easily be reproduced with type parameters on methods. So that wasn't a very compelling example. (I might be missing something obvious. Please correct me if so.)
乍一看,这有什么用并不是很明显。Heiko Seeberger 在这里发布了一个依赖方法类型的简单示例,从评论中可以看出,可以使用方法的类型参数轻松复制。所以这不是一个很有说服力的例子。(我可能遗漏了一些明显的东西。如果是这样,请纠正我。)
What are some practical and useful examples of use cases for dependent method types where they are clearly advantageous over the alternatives?
对于依赖方法类型的用例,有哪些实用且有用的示例,它们明显优于替代方法?
What interesting things can we do with them that weren't possible/easy before?
我们可以用它们做哪些以前不可能/容易的有趣的事情?
What do they buy us over the existing type system features?
他们为我们购买了现有类型系统功能的什么?
Also, are dependent method types analogous to or drawing inspiration from any features found in the type systems of other advanced typed languages such as Haskell, OCaml?
此外,从属方法类型是否类似于 Haskell、OCaml 等其他高级类型语言的类型系统中的任何功能或从中汲取灵感?
采纳答案by Miles Sabin
More or less any use of member (ie. nested) types can give rise to a need for dependent method types. In particular, I maintain that without dependent method types the classic cake pattern is closer to being an anti-pattern.
或多或少任何成员(即嵌套)类型的使用都会引起对依赖方法类型的需求。特别是,我认为没有依赖方法类型的经典蛋糕模式更接近于反模式。
So what's the problem? Nested types in Scala are dependent on their enclosing instance. Consequently, in the absence of dependent method types, attempts to use them outside of that instance can be frustratingly difficult. This can turn designs which initially seem elegant and appealing into monstrosities which are nightmarishly rigid and difficult to refactor.
所以有什么问题?Scala 中的嵌套类型依赖于它们的封闭实例。因此,在没有依赖方法类型的情况下,尝试在该实例之外使用它们可能会非常困难。这可以将最初看起来优雅和吸引人的设计变成可怕的僵化并且难以重构的怪物。
I'll illustrate that with an exercise I give during my Advanced Scala training course,
我将通过我在高级 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
}
It's an example of the classic cake pattern: we have a family of abstractions which are gradually refined through a heirarchy (ResourceManager/Resourceare refined by FileManager/Filewhich are in turn refined by NetworkFileManager/RemoteFile). It's a toy example, but the pattern is real: it's used throughout the Scala compiler and was used extensively in the Scala Eclipse plugin.
这是典型的蛋糕模式的一个例子:我们有一个家庭的抽象,它们通过层次结构逐步细化的(ResourceManager/Resource是由精制FileManager/File这是又被细化NetworkFileManager/ RemoteFile)。这是一个玩具示例,但模式是真实的:它在整个 Scala 编译器中使用,并在 Scala Eclipse 插件中广泛使用。
Here's an example of the abstraction in use,
这是使用中的抽象的一个例子,
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
Note that the path dependency means that the compiler will guarantee that the testHashand testDuplicatesmethods on NetworkFileManagercan only be called with arguments which correspond to it, ie. it's own RemoteFiles, and nothing else.
请注意,路径依赖意味着编译器将保证只能使用与其对应的参数调用testHash和testDuplicates方法NetworkFileManager,即。它是自己的RemoteFiles,没有别的。
That's undeniably a desirable property, but suppose we wanted to move this test code to a different source file? With dependent method types it's trivially easy to redefine those methods outside the ResourceManagerhierarchy,
不可否认,这是一个理想的属性,但假设我们想将此测试代码移动到不同的源文件中?对于依赖方法类型,在ResourceManager层次结构之外重新定义这些方法非常容易,
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
Note the uses of dependent method types here: the type of the second argument (rm.Resource) depends on the value of the first argument (rm).
注意这里依赖方法类型的使用:第二个参数 ( rm.Resource)的类型取决于第一个参数 ( rm)的值。
It is possible to do this without dependent method types, but it's extremely awkward and the mechanism is quite unintuitive: I've been teaching this course for nearly two years now, and in that time, noone has come up with a working solution unprompted.
可以在没有依赖方法类型的情况下做到这一点,但它非常笨拙,而且机制非常不直观:我已经教这门课程近两年了,在那段时间里,没有人自发地想出一个可行的解决方案。
Try it for yourself ...
自己试试吧...
// 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)
After a short while struggling with it you'll probably discover why I (or maybe it was David MacIver, we can't remember which of us coined the term) call this the Bakery of Doom.
经过一段时间的努力,您可能会发现为什么我(或者可能是 David MacIver,我们不记得我们中谁创造了这个词)称其为末日面包店。
Edit:consensus is that Bakery of Doom was David MacIver's coinage ...
编辑:一致认为末日面包店是大卫麦基弗的造币......
For the bonus: Scala's form of dependent types in general (and dependent method types as a part of it) was inspired by the programming language Beta... they arise naturally from Beta's consistent nesting semantics. I don't know of any other even faintly mainstream programming language which has dependent types in this form. Languages like Coq, Cayenne, Epigram and Agda have a different form of dependent typing which is in some ways more general, but which differs significantly by being part of type systems which, unlike Scala, don't have subtyping.
额外奖励:Scala 的依赖类型的形式(以及依赖方法类型作为它的一部分)的灵感来自于编程语言Beta......它们自然产生于 Beta 的一致嵌套语义。我不知道任何其他甚至微弱的主流编程语言都具有这种形式的依赖类型。Coq、Cayenne、Epigram 和 Agda 之类的语言具有不同形式的依赖类型,这在某些方面更通用,但作为类型系统的一部分而显着不同,与 Scala 不同的是,类型系统没有子类型。
回答by Alexey Romanov
trait Graph {
type Node
type Edge
def end1(e: Edge): Node
def end2(e: Edge): Node
def nodes: Set[Node]
def edges: Set[Edge]
}
Somewhere else we can statically guarantee that we aren't mixing up nodes from two different graphs, e.g.:
在其他地方,我们可以静态地保证我们不会混淆来自两个不同图形的节点,例如:
def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ...
Of course, this already worked if defined inside Graph, but say we can't modify Graphand are writing a "pimp my library" extension for it.
当然,如果在 里面定义,这已经起作用了Graph,但是说我们不能修改Graph并且正在为它编写“pimp my library”扩展。
About the second question: types enabled by this feature are farweaker than complete dependent types (See Dependently Typed Programming in Agdafor a flavor of that.) I don't think I've seen an analogy before.
关于第二个问题:此功能启用的类型远弱于完全依赖类型(请参阅Agda中的Dependently Typed Programming in Agda。)我想我以前没有见过类比。
回答by Shelby Moore III
This new feature is needed when concreteabstract type members are used instead of type parameters. When type parameters are used, the family polymorphismtype dependency can be expressed in the latest and some older versions of Scala, as in the following simplified example.
当使用具体抽象类型成员而不是类型参数时,需要此新功能。当使用类型参数时,家族多态类型依赖可以在最新和一些旧版本的 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, "")
^
回答by Shelby Moore III
I'm developing a modelfor the interoption of a form of declarative programming with environmental state. The details aren't relevant here (e.g. details about callbacks and conceptual similarity to the Actor model combined with a Serializer).
我正在开发一种模型,用于声明式编程形式与环境状态的交互。此处的详细信息不相关(例如,有关回调的详细信息以及与结合序列化程序的 Actor 模型的概念相似性)。
The relevant issue is state values are stored in a hash map and referenced by a hash key value. Functions input immutable arguments that are values from the environment, may call other such functions, and write state to the environment. But functions are not allowed to readvalues from the environment (so the internal code of the function is not dependent on the order of state changes and thus remains declarative in that sense). How to type this in Scala?
相关问题是状态值存储在哈希映射中并由哈希键值引用。函数输入作为环境值的不可变参数,可以调用其他此类函数,并将状态写入环境。但是函数不允许从环境中读取值(因此函数的内部代码不依赖于状态更改的顺序,因此在这个意义上仍然是声明性的)。如何在 Scala 中输入这个?
The environment class must have an overloaded method which inputs such a function to call, and inputs the hash keys of the arguments of the function. Thus this method can call the function with the necessary values from the hash map, without providing public read access to the values (thus as required, denying functions the ability to read values from the environment).
环境类必须有一个重载方法,该方法输入要调用的函数,并输入函数参数的哈希键。因此,此方法可以使用哈希映射中的必要值调用函数,而无需提供对值的公共读取访问权限(因此,根据需要,拒绝函数从环境中读取值的能力)。
But if these hash keys are strings or integer hash values, the static typing of the hash map element type subsumesto Any or AnyRef (hash map code not shown below), and thus a run-time mismatch could occur, i.e. it would be possible to put any type of value in a hash map for a given hash key.
但是,如果这些散列键是字符串或整数的散列值,所述散列映射元素类型的静态类型涵括于任何或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
}
Although I didn't test the following, in theory I can get the hash keys from class names at runtime employing classOf, so a hash key is a class name instead of a string (using Scala's backticks to embed a string in a class name).
虽然我没有测试以下内容,但理论上我可以在运行时从类名中获取哈希键classOf,因此哈希键是类名而不是字符串(使用 Scala 的反引号将字符串嵌入到类名中)。
trait DependentHashKey {
type ValueType
}
trait `the hash key string` extends DependentHashKey {
type ValueType <: SomeType
}
So static type safety is achieved.
这样就实现了静态类型安全。
def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A

