Scala组合器解析器,>>代表什么意思?

5
我有一点困惑于Scala中的">>"。在Scala parser combinators parsing xml?中,丹尼尔说它可以根据前一个解析器的结果对解析器进行参数化。能否给我一些示例/提示?我已经阅读了Scaladoc,但仍然不理解。

谢谢

2个回答

16

就像我说的那样,它用于为解析器参数化,但让我们通过一个示例来说明清楚。

让我们从一个简单的解析器开始,该解析器解析一个数字后跟一个单词:

def numberAndWord = number ~ word
def number        = "\\d+".r
def word          = "\\w+".r

使用RegexParsers,可以解析"3 fruits"之类的内容。

现在,如果你想要这些“n个物品”的列表。例如,“3个水果:香蕉、苹果、橙子”。我们尝试解析一下,看看情况如何。

首先,如何解析“N”个物品?碰巧有一个repN方法:

def threeThings = repN(3, word)

这将解析 "banana apple orange",但不会解析 "banana, apple, orange"。我需要一个分隔符。repsep 提供了这个功能,但它不能让我指定我想要的重复次数。因此,让我们自己提供分隔符:

def threeThings = word ~ repN(2, "," ~> word)

好的,那就这样吧。我们现在可以按照以下三个事情,写出完整的例子:

def listOfThings = "3" ~ word ~ ":" ~ threeThings
def word         = "\\w+".r
def threeThings  = word ~ repN(2, "," ~> word)

这种方法有点可行,但我正在将 3 固定为 "N"。我希望用户可以自己指定数量。这就是 >>,也被称为 into(是的,对于 Parser,它确实是 flatMap)发挥作用的地方。首先,让我们更改 threeThings

def things(n: Int) = n match {
  case 1          => word ^^ (List(_))
  case x if x > 1 => word ~ repN(x - 1, "," ~> word) ^^ { case w ~ l => w :: l }
  case x          => err("Invalid repetitions: "+x)
}

这比你预期的要稍微复杂一些,因为我强制它返回Parser[List[String]]。但是我如何向它传递参数呢?我的意思是,这样做行不通:

def listOfThings = number ~ word ~ ":" ~ things(/* what do I put here?*/)

但我们可以这样重写:

def listOfThings = (number ~ word <~ ":") >> {
  case n ~ what => things(n.toInt)
}
那几乎够好了,除了现在我失去了nwhat: 它只返回"List(banana, apple, orange)",而不是应该有多少个,以及它们是什么。我可以这样做:
def listOfThings   = (number ~ word <~ ":") >> {
  case n ~ what => things(n.toInt) ^^ { list => new ~(n.toInt, new ~(what, list)) }
}
def number         = "\\d+".r
def word           = "\\w+".r
def things(n: Int) = n match {
  case 1          => word ^^ (List(_))
  case x if x > 1 => word ~ repN(x - 1, "," ~> word) ^^ { case w ~ l => w :: l }
  case x          => err("Invalid repetitions: "+x)
}

最后一句话。你可能会想知道,自问自答说:"flatMap是什么意思?不是一个单子/for-comprehension的东西吗?" 是的,没错! :-) 这是另一种编写listOfThings的方式:

def listOfThings   = for {
  nOfWhat  <- number ~ word <~ ":"
  n ~ what = nOfWhat
  list     <- things(n.toInt)
}  yield new ~(n.toInt, new ~(what, list))

我不使用 Scala 中的 filterwithFilter,因为这在 Parsers 中没有实现,所以我不执行 n ~ what <- number ~ word <~ ":"。但是,以下是另一种编写方式,它并没有完全相同的语义,但会产生相同的结果:

def listOfThings   = for {
  n    <- number
  what <- word
  _    <- ":" : Parser[String]
  list <- things(n.toInt)
}  yield new ~(n.toInt, new ~(what, list))

这甚至可能让人想到,也许声称 "单子无所不在" 的说法有些道理。 :-)


谢谢,但还有一件事。我仍然不理解 { list => new ~(n.toInt, new ~(what, list)) } 中的 new。因为我对Scala还比较陌生,所以我只知道使用new从类中生成实例。 - Tg.
2
@Tg。这正是发生在这里的事情。当你使用~运算符解析某个东西时,结果将是~的一个实例。我本可以——或者说我应该——返回一个元组,因为这样做更容易。但是,我想保留示例代码中的原始返回值。 - Daniel C. Sobral
1
@Tg。不是真的。Scala有函数是对象。然而,在这里,“”类和“Parser”的“”运算符是有意设计成相同的名称,“~”运算符返回一个Parser [〜[T,U]],其中TU是此运算符应用于的解析器的类型。但是,类和运算符可以完全不同的名称。顺便说一下,这种二元运算符/类的二重性也可以在“::”中找到。 - Daniel C. Sobral
感谢您清晰的解释。看来我还有很多要学习的。但是,是的,我在之前的评论中真正想表达的是“我将一个~误认为是普通运算符”,所以第一次让我感到困惑。 - Tg.

5
方法>>接受一个函数,该函数给出解析器的结果并使用它来构造一个新的解析器。正如所述,这可以用于将解析器参数化为先前解析器的结果。
例子
以下解析器解析具有n + 1个整数值的行。第一个值n表示要跟随的值的数量。首先解析此第一个整数,然后使用此解析的结果构造解析器,以解析n个其他整数。
解析器定义
以下行假定您可以使用parseInt:Parser [Int]解析整数。它首先解析一个整数值n,然后使用>>解析n个附加整数,这些整数形成解析器的结果。因此,初始n不会由解析器返回(尽管它是返回列表的大小)。
def intLine: Parser[Seq[Int]] = parseInt >> (n => repN(n,parseInt))

有效输入

1 42
3 1 2 3
0

无效输入

0 1
1
3 42 42

这不是flatMap的别名吗? - paradigmatic
根据Scaladocs的说法,它是into的别名。根据源代码,>>调用into,而into又调用了flatMap - ziggystar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接