我注意到Scala标准库使用两种不同的策略来组织类,特征和单例对象。
使用其成员为导入的包。例如,这是您访问scala.collection.mutable.ListBuffer
的方式。这种技术很熟悉来自Java,Python等。
使用特征的类型成员。例如,这是您访问Parser
类型的方式。您首先需要混合scala.util.parsing.combinator.Parsers
。这种技术不熟悉来自Java,Python等,并且在第三方库中使用的并不多。
我猜(2)的一个优点是它组织了方法和类型,但根据Scala 2.8的包对象,可以使用(1)完成相同的操作。为什么要有这两种策略?应该何时使用?
答案 0 :(得分:5)
此处注释的术语是 path-dependent types 。那是你所说的2号选项,我只会说它。除非您碰巧遇到问题,否则您应该始终选择1号选项。
您错过的是Parser
类引用了Parsers
类中定义的内容。事实上,Parser
类本身取决于input
上定义的Parsers
:
abstract class Parser[+T] extends (Input => ParseResult[T])
类型Input
的定义如下:
type Input = Reader[Elem]
Elem
是抽象的。例如,考虑RegexParsers
和TokenParsers
。前者将Elem
定义为Char
,而后者将其定义为Token
。这意味着每个Parser
都不同。更重要的是,因为Parser
是Parsers
的子类,Scala编译器将确保在编译时您没有将RegexParsers
的{{1}}传递给{{1} }} 或相反亦然。事实上,您甚至无法将Parser
的{{1}}个实例的TokenParsers
传递给它的另一个实例。
答案 1 :(得分:4)
第二个也被称为Cake pattern。 它的好处是,具有混合特征的类中的代码变得独立于该特征中的方法和类型的特定实现。它允许使用特征的成员而不知道它们的具体实现是什么。
trait Logging {
def log(msg: String)
}
trait App extends Logging {
log("My app started.")
}
上面,Logging
特征是App
的要求(要求也可以用自我类型表示)。然后,在应用程序的某个时刻,您可以决定实现的内容,并将实现特性混合到具体的类中。
trait ConsoleLogging extends Logging {
def log(msg: String) = println(msg)
}
object MyApp extends App with ConsoleLogging
这比导入更有优势,因为您的代码片段的要求不受import
语句定义的实现的约束。此外,它允许您构建和分发一个API,该API可以在其他地方的其他构建中使用,前提是通过混合具体实现来满足其要求。
但是,使用此模式时需要注意一些事项。
我想图书馆设计师并不认为上述任何问题是Parsers
所关注的问题。