4

我注意到 Scala 标准库使用两种不同的策略来组织类、特征和单例对象。

  1. 使用其成员是导入的包。例如,这就是您如何访问scala.collection.mutable.ListBuffer. 这种技术熟悉来自 Java、Python 等。

  2. 使用特征的类型成员。例如,这是您访问该Parser类型的方式。您首先需要混合scala.util.parsing.combinator.Parsers. 这种技术并不熟悉来自 Java、Python 等,并且在第三方库中使用得不多。

我想(2)的一个优点是它组织了方法和类型,但是根据 Scala 2.8 的包对象,同样可以使用(1)来完成。为什么有这两种策略?什么时候应该使用每个?

4

2 回答 2

5

The nomenclature of note here is path-dependent types. That's the option number 2 you talk of, and I'll speak only of it. Unless you happen to have a problem solved by it, you should always take option number 1.

What you miss is that the Parser class makes reference to things defined in the Parsers class. In fact, the Parser class itself depends on what input has been defined on Parsers:

abstract class Parser[+T] extends (Input => ParseResult[T])

The type Input is defined like this:

type Input = Reader[Elem]

And Elem is abstract. Consider, for instance, RegexParsers and TokenParsers. The former defines Elem as Char, while the latter defines it as Token. That means the Parser for the each is different. More importantly, because Parser is a subclass of Parsers, the Scala compiler will make sure at compile time you aren't passing the RegexParsers's Parser to TokenParsers or vice versa. As a matter of fact, you won't even be able to pass the Parser of one instance of RegexParsers to another instance of it.

于 2011-03-16T23:06:40.637 回答
4

第二种也称为蛋糕图案。它的好处是,类中混合了特征的代码变得独立于该特征中方法和类型的特定实现。它允许在不知道具体实现的情况下使用 trait 的成员。

trait Logging {
  def log(msg: String)
}

trait App extends Logging {
  log("My app started.")
}

上面,Loggingtrait 是对的需求App(需求也可以用自类型表示)。然后,在您的应用程序中的某个时刻,您可以决定实现将是什么并将实现特征混合到具体类中。

trait ConsoleLogging extends Logging {
  def log(msg: String) = println(msg)
}

object MyApp extends App with ConsoleLogging

这比导入有优势,因为您的代码段的要求不受import语句定义的实现的约束。此外,它允许您构建和分发 API,该 API 可以在其他地方的不同构建中使用,前提是通过混合具体实现来满足其要求。

但是,使用此模式时需要注意一些事项。

  1. 特征内定义的所有类都将引用外部类。这可能是一个涉及性能的问题,或者当您使用序列化时(当外部类不可序列化时,或者更糟糕的是,如果是,但您不希望它被序列化)。
  2. 如果您的“模块”变得非常大,您将拥有一个非常大的特征和一个非常大的源文件,或者必须将模块特征代码分布在多个文件中。这可能会导致一些样板文件。
  3. 它可能会迫使您必须使用此范例编写整个应用程序。在不知不觉中,每个课程都必须混合其要求。
  4. 除非您使用某种手写委托,否则必须在编译时知道具体实现。您不能根据运行时可用的值动态混合实现特征。

我猜图书馆设计者并没有将上述任何一个问题视为Parsers关注的问题。

于 2011-03-16T17:15:05.997 回答