我将从一个使用 Scala 的示例开始Option
类型,在一些重要方面类似于Parser
,但可以更容易推理。假设我们有以下两个值:
val fullBox: Option[String] = Some("13")
val emptyBox: Option[String] = None
Option
是一元的,这意味着(部分)我们可以map
其内容的函数:
scala> fullBox.map(_.length)
res0: Option[Int] = Some(2)
scala> emptyBox.map(_.length)
res1: Option[Int] = None
只关心是否Option
是否已满,在这种情况下我们可以使用map
使用忽略其参数的函数:
scala> fullBox.map(_ => "Has a value!")
res2: Option[String] = Some(Has a value!)
scala> emptyBox.map(_ => "Has a value!")
res3: Option[String] = None
事实是Option
是一元的也意味着我们可以应用于Option[A]
一个函数,它需要一个A
并返回一个Option[B]
并得到一个Option[B]
。在本示例中,我将使用一个尝试将字符串解析为整数的函数:
def parseIntString(s: String): Option[Int] = try Some(s.toInt) catch {
case _: Throwable => None
}
现在我们可以编写以下内容:
scala> fullBox.flatMap(parseIntString)
res4: Option[Int] = Some(13)
scala> emptyBox.flatMap(parseIntString)
res5: Option[Int] = None
scala> Some("not an integer").flatMap(parseIntString)
res6: Option[Int] = None
这都与你的问题相关,因为Parser
也是一元的,并且它有map
and flatMap
方法的工作方式与上面的方法非常相似Option
。它还有一堆令人困惑的运算符(其中我之前也吐槽过),包括你提到的那些,这些运算符只是别名map
and flatMap
:
(parser ^^ transformation) == parser.map(transformation)
(parser ^^^ replacement) == parser.map(_ => replacement)
(parser >> nextStep) == parser.flatMap(nextStep)
例如,您可以编写以下内容:
object MyParser extends RegexParsers {
def parseIntString(s: String) = try success(s.toInt) catch {
case t: Throwable => err(t.getMessage)
}
val digits: Parser[String] = """\d+""".r
val numberOfDigits: Parser[Int] = digits ^^ (_.length)
val ifDigitsMessage: Parser[String] = digits ^^^ "Has a value!"
val integer: Parser[Int] = digits >> parseIntString
}
每个解析器的行为方式相当于其中之一Option
上面的例子。