什么是Scala中的头等舱模块?

Roy*_*man 10 scala

当说scala通过对象语法提供一流的模块支持时,这是什么意思?词汇表中没有任何内容甚至提到这句话,但我现在已经遇到过两次并且无法破译它.在这篇关于适配器的博客文章中有人说过.

Dan*_*ral 13

"模块"是可插拔的软件,有时也称为"软件包".它通过一组明确定义的接口提供功能,这些接口声明了它提供的内容和需要的内容.最后,它是可以互换的.

很少有直接支持模块的语言,主要是因为虽然支持声明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
}
Run Code Online (Sandbox Code Playgroud)

和Clock将是一个模块定义,例如:

trait Clock {
  // What it provides
  type AClock <: Clock
  trait Clock {
    def now(): Long
  }
  def aClock: AClock
}
Run Code Online (Sandbox Code Playgroud)

我的模块本身可能如下所示:

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
}
Run Code Online (Sandbox Code Playgroud)

时钟模块将被类似地定义.应用程序可以声明为模块的组合:

trait application extends Clock with Benchmark with ...
Run Code Online (Sandbox Code Playgroud)

当然,不需要声明依赖关系,因为已经提供了它们.然后,您可以组合提供构建应用程序要求的模块:

object Application extends MyModule with JavaClock with ...
Run Code Online (Sandbox Code Playgroud)

这将把MyModule的要求与JavaClock提供的实现联系起来.上面的内容仍然需要一些共享的知识,因为"时钟"可能是提供的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
}
Run Code Online (Sandbox Code Playgroud)

现在任何提供now():Long方法的类都可以用来满足需求,而不需要任何桥接.当然,如果方法的名称是"millis():Long"而不是"now():Long",我仍然搞砸了,那种"绑定"是语言提供模块支持可能解决的问题,虽然不是斯卡拉.此外,由于JVM的工作原理,也存在性能损失.

那么,这就是模块和模块支持.最后是一流的模块.对X的第一类支持意味着X可以作为值进行操作.例如,Scala具有对函数的第一类支持,这意味着我可以将函数传递给方法,将其存储在变量中,映射中等等.

对模块的第一类支持基本上是实例化,尽管可以使用"对象"来创建该模块的单例,然后传递它(我将在下面进一步讨论其优点).所以,我可以这样做:

object myBenchmark extends MyModule with JVMClock
Run Code Online (Sandbox Code Playgroud)

并将myBenchmark作为参数传递给需要这种模块的方法.

Scala中有两个元素可以完成所有工作:抽象类型和路径相关类型.

抽象类型,即"类型"声明,使得一段代码可以声明它将使用类型X,这不会由谁调用或实例化它来定义,而是在模块获取时组成.

依赖于路径的类型使得可以在不完全不安全的情况下使用模块,但没有限制性以至于不允许任何事情.假设我这样做:

val b: MyModule.Clock = MyModule.aClock
Run Code Online (Sandbox Code Playgroud)

让我们说我在Benchmark上有一个方法,它将Clock作为参数.我可以在MyModule上调用该方法,将b作为参数传递,因为Scala知道b的Clock是绑定到MyModule的时钟.如果我尝试将b传递给另一个实现Benchmark的模块,Scala就不会让我这样做.也就是说,我可以从Benchmark中获取特定于抽象类型Benchmark的值 - 除了模块实现之外都不知道 - 并将其反馈给Benchmark而不是其他Benchmark实现.