当说scala通过对象语法提供了一流的模块支持是什么意思?词汇表中没有提到这个短语,但我已经遇到过两次了,而且无法解密。在这篇关于适配器的博客文章中提到了这一点。
当说scala通过对象语法提供了一流的模块支持是什么意思?词汇表中没有提到这个短语,但我已经遇到过两次了,而且无法解密。在这篇关于适配器的博客文章中提到了这一点。
"模块"是可插拔的软件部件,有时也称为“包(package)"。它通过一组明确定义的接口提供功能,声明了它提供什么和需要什么。最后,它是可互换的。
大多数语言都没有直接支持模块,这主要是因为虽然支持声明API很常见,但不支持声明依赖或需求。在流行语言中,库通常通过依赖于“标准”库提供的类型或要求使用实现其提供的API的对象进行初始化来进行接口连接。
所以,如果我想制作一个基准测试模块,我通常会使用标准库提供的计时工具,或者最糟糕的情况下,我会声明一个计时器类型,并请求在模块的功能可以使用之前用实现该类型的类进行初始化。
然而,当模块支持可用时,我将不仅声明我提供的基准测试接口,而且还将声明我需要一个“时钟模块”——一个导出我所需接口的模块。
我的模块的客户端不需要做任何事情就可以使用我的接口——它可以直接使用。或者它甚至不声明要使用我的基准测试模块,而是声明它对基准测试模块的需求。
什么能够满足需求只有在应用程序级别(模块是应用程序的组件)上才会被确定。在那个点上,它将声明它将使用该客户端、我的基准测试和一个实现我所需时钟的模块。
如果您了解Guice,这可能很熟悉。Guice解决的问题很大程度上是由于Java编程语言中缺乏模块支持造成的。
那么,在Scala中,模块支持是如何工作的呢?嗯,我的模块接口可能看起来像这样:
trait Benchmark extends Clock // What I need {
// What I provide
type ABench <: Bench
trait Bench {
def measure(task: => Unit): Long
}
def aBench: ABench
}
时钟将被定义为一个模块,就像这样:
trait Clock {
// What it provides
type AClock <: Clock
trait Clock {
def now(): Long
}
def aClock: AClock
}
我的模块本身可能看起来像这样:
trait MyModule extends Benchmark {
class ABench extends Bench {
def measure(task: => Unit): Long = {
val measurements = for(_ <- 1 to 10) yield {
val start = aClock.now()
task
val end = aClock.now()
end - start
}
measurements / 10
}
}
object aBench extends ABench
}
时钟模块将被类似地定义。一个应用程序可以声明为由多个模块组合而成:
trait application extends Clock with Benchmark with ...
当然,虽然不需要声明依赖关系,因为已经提供了。你可以将提供应用程序构建要求的模块组合起来使用:
object Application extends MyModule with JavaClock with ...
这将使得MyModule的要求与JavaClock提供的实现相链接。上面的内容仍需要一些共享知识,因为"Clock"很可能是我提供的API。当然,一个人可以编写代理,但这并不是即插即用的。如果我像这样声明我的Benchmark模块,Scala可以更进一步:
trait Benchmark {
type Clock = {
def now(): Long
}
def aClock: Clock
// What I provide
type ABench <: Bench
trait Bench {
def measure(task: => Unit): Long
}
def aBench: ABench
}
现在,任何提供now(): Long方法的类都可以被用来满足要求,而不需要使用桥接方法。当然,如果方法名称是"millis(): Long"而不是"now(): Long",那么我就无法使用,这种"绑定"是某些支持模块的语言可能会解决的问题,但Scala不会。此外,由于JVM的工作方式,也会存在性能损失。
所以,这就是模块和模块支持。最后是第一流模块(first class module)。对X的第一流支持意味着X可以像值一样被操纵。例如,Scala对函数有第一流支持,这意味着我可以将函数传递给方法,在变量、映射中存储函数等。
对于模块的第一流支持基本上是实例化,虽然可以使用"对象"来创建该模块的单例,然后传递它(我下面进一步讨论了它的优点)。所以,我可以这样做:
object myBenchmark extends MyModule with JVMClock
并将 myBenchmark 作为参数传递给需要此类模块的方法。
在Scala中,有两个元素使所有这些工作:抽象类型和路径相关类型。
抽象类型是“类型”声明,它使代码可以声明将使用类型X,该类型不会由调用或实例化它的人定义,而是在组合模块的时刻定义。
路径相关类型使得可以在不完全不安全的情况下使用模块,但不至于过于限制而不允许任何东西。假设我这样做:
val b: MyModule.Clock = MyModule.aClock
假设我在Benchmark上有一个方法,它需要一个Clock作为参数。我可以在MyModule上调用该方法并将b作为参数传递进去,因为Scala知道b的Clock是绑定到MyModule的。而如果我试图将b传递给实现Benchmark的另一个模块,Scala将不会让我这样做。也就是说,我可以从Benchmark中获取特定于Benchmark抽象类型的值--仅对模块实现者未知--并将其反馈回该Benchmark,但不能反馈回其他Benchmark实现。