5

我可以创建一个解析器,它可以处理两个或三个数字,用逗号分隔,如下所示:

number :: Parser Int
number = many1 digit >>= return . read <?> "number"

numbers = do
  n1 <- number
  n2 <- char ':' >> number
  n3 <- optionMaybe $ char ':' >> number
  return ... -- return all of n1, n2, n3

只有数字很重要,其余的可以丢弃。有没有办法连接中间解析结果(n1,n2,n3)来处理它input?例如 Scala 的 parser-combinator 可以这样做:

def numbers: Parser[Int ~ Int ~ Option[Int]] = // only the important numbers are returned
  number ~ (":" ~> number) ~ opt(":" ~> number)

我想这样做是为了在不同的地方对解析器进行模式匹配。例如,在 Scala 中,我可以这样做:

val result = input.parseAs(numbers) {
  case n1 ~ n2 ~ None => // work with n1,n2
  case n1 ~ n2 ~ Some(n3) => // work with n1,n2,n3
}

其中 input 是要解析的字符串。parsec 是否具有允许类似行为的内置函数?如果不是我自己如何建立这样的行为?

4

1 回答 1

8

您可以通过使用应用函子来做到这一点。模式一般是:

import Control.Applicative

f <$> a1 <*> a2 <*> a3

f是一些在这种情况下接受 3 个参数的函数,并且a1,a2a3是可作为参数传递给 的值的应用函子f,例如 if f :: Int -> Int -> Int -> Foo,a1, a2, a3可以有 type Parser Int。仿函数a1, a2, a3将按顺序应用,它们的结果将被收集并映射到函数上f

在你的情况下,你想做:

numbers :: Parser (Int, Int, Maybe Int)
numbers =
  (,,)
  <$> number
  <*> (char ':' *> number)
  <*> optionMaybe (char ':' *> number)

(,,)是一个 3 元组的构造函数,所以它是一个接受 3 个参数并返回一个 3 元组的函数。使用该模式传递 3 个解析器会将<$>..<*>..3 元组构造函数的应用提升到此处使用的函子中,Parser在这种情况下,因此整个表达式返回包装在函子中的映射函数的结果,即Parser (Int, Int, Maybe Int).

您也可以使用liftA3 f a1 a2 a3代替f <$> a1 <*> a2 <*> a3; 这两个表达式是等价的。

PS。您也可以使用应用函子进行定义number(monad 接口更“重量级”,我个人尽量避免使用它):

number :: Parser Int
number = read <$> many1 digit <?> "number"
于 2012-03-11T19:55:17.617 回答