了解 Scala 隐式

Understanding Scala Implicits

在阅读 Chiusano 和 Bjarnason 的 Functional Programming in Scala 时,我在第 9 章 Parser Combinators 中遇到了以下代码:

trait Parsers[ParseError, Parser[+_]] { self =>
  ...
  def or[A](s1: Parser[A], s2: Parser[A]): Parser[A]
  implicit def string(s: String): Parser[String]
  implicit def operators[A](p: Parser[A]) = ParserOps[A](p)
  implicit def asStringParser[A](a: A)(implicit f: A => Parser[String]):
    ParserOps[String] = ParserOps(f(a))

  case class ParserOps[A](p: Parser[A]) {
    def |[B>:A](p2: Parser[B]): Parser[B] = self.or(p,p2)
    def or[B>:A](p2: => Parser[B]): Parser[B] = self.or(p,p2)
  }
}

我知道如果在编译期间存在类型不兼容或缺少参数,Scala 编译器会查找缺少的函数,该函数将不匹配的类型转换为所需的类型或范围内具有所需类型的变量分别拟合缺失的参数。

如果一个字符串出现在需要Parser[String]的地方,则需要调用上述trait中的字符串函数将字符串转换为Parser[String].

但是,我很难理解 operatorsasStringParser 函数。这些是我的问题:

  1. 对于隐式运算符函数,为什么没有 return 类型?
  2. 为什么 ParserOps 定义为 case class,为什么 |or 函数不能在 Parsers 特征本身中定义?
  3. asStringParser 到底想完成什么?它在这里的目的是什么?
  4. 为什么需要 self?书上说,"Use self to explicitly disambiguate reference to the or method on the trait," 但这是什么意思?

我真的很喜欢这本书,但是本章中高级语言特定结构的使用阻碍了我的进步。如果你能向我解释这段代码是如何工作的,那将很有帮助。我知道目标是让库 "nicer" 通过 |or 等运算符使用,但不明白这是如何完成的。

  1. 每个方法都有一个 return 类型。在本例中,它是 ParserOps[A]。你不必明确地写出来,因为在这种情况下它可以被自动推断出来。
  2. 可能是因为伴随对象中自动提供的 ParserOps.apply-factory 方法。您在构造函数中需要的 val 更少,并且您不需要 new 关键字来实例化 ParserOps。它不用于模式匹配,所以,你可以用普通的(非case)class做同样的事情,没关系。
  3. 这是 "pimp-my-library" 模式。它将方法 |or 附加到 Parser,而不强制 Parser 从任何东西继承。这样,您稍后可以将 Parser 声明为 ParserState => Result[A] 之类的东西,但您仍然可以使用方法 |or(即使 Function1[ParserState, Result[A]]没有它们)。
  4. 您可以将 |or 直接放在 Parsers 中,但您必须使用语法

    |(a, b)
    or(a, b)
    

    而不是更好的

    a | b
    a or b
    

Scala中没有"real operators",一切都是方法。如果你想实现一个表现得像中缀运算符的方法,你就完全按照书中的说明去做。