我先给你讲讲 tl;dr
我正在尝试使用状态单子变压器Scalaz 7 https://github.com/scalaz/scalaz通过解析器线程化额外的状态,如果不编写一个,我就很难做任何有用的事情lot of t m a -> t m b
的版本m a -> m b
方法。
解析问题示例
假设我有一个包含嵌套括号且其中包含数字的字符串:
val input = "((617)((0)(32)))"
我还有一串新的变量名称(在本例中为字符):
val names = Stream('a' to 'z': _*)
我想从流的顶部提取一个名称并将其分配给每个括号
解析它时的表达式,然后将该名称映射到表示
括号的内容,其中嵌套的括号表达式(如果有)替换为它们的
名称。
为了使这一点更加具体,我希望上面的示例输入的输出如下所示:
val target = Map(
'a' -> "617",
'b' -> "0",
'c' -> "32",
'd' -> "bc",
'e' -> "ad"
)
在给定级别上可能存在一串数字或任意多个子表达式,但这两种内容不会混合在单个括号表达式中。
为了简单起见,我们假设名称流永远不会
包含重复项或数字,并且它将始终包含足够的
我们输入的名称。
使用带有一些可变状态的解析器组合器
上面的例子是解析问题的稍微简化的版本这个堆栈溢出问题 https://stackoverflow.com/q/12442615/334519.
I 回答了这个问题 https://stackoverflow.com/a/12443270/334519和
解决方案大致如下:
import scala.util.parsing.combinator._
class ParenParser(names: Iterator[Char]) extends RegexParsers {
def paren: Parser[List[(Char, String)]] = "(" ~> contents <~ ")" ^^ {
case (s, m) => (names.next -> s) :: m
}
def contents: Parser[(String, List[(Char, String)])] =
"\\d+".r ^^ (_ -> Nil) | rep1(paren) ^^ (
ps => ps.map(_.head._1).mkString -> ps.flatten
)
def parse(s: String) = parseAll(paren, s).map(_.toMap)
}
这还不错,但我更愿意避免可变状态。
我想要的是
哈斯克尔的Parsec http://hackage.haskell.org/package/parsec-3.1.3图书馆制作
将用户状态添加到解析器非常简单:
import Control.Applicative ((*>), (<$>), (<*))
import Data.Map (fromList)
import Text.Parsec
paren = do
(s, m) <- char '(' *> contents <* char ')'
h : t <- getState
putState t
return $ (h, s) : m
where
contents
= flip (,) []
<$> many1 digit
<|> (\ps -> (map (fst . head) ps, concat ps))
<$> many1 paren
main = print $
runParser (fromList <$> paren) ['a'..'z'] "example" "((617)((0)(32)))"
这是我上面的 Scala 解析器的相当简单的翻译,但没有可变状态。
我尝试过的
我试图使用 Scalaz 的状态单子转换器尽可能接近秒差距解决方案,所以而不是Parser[A]
我正在与StateT[Parser, Stream[Char], A]
。
我有一个“解决方案”,允许我编写以下内容:
import scala.util.parsing.combinator._
import scalaz._, Scalaz._
object ParenParser extends ExtraStateParsers[Stream[Char]] with RegexParsers {
protected implicit def monadInstance = parserMonad(this)
def paren: ESP[List[(Char, String)]] =
(lift("(" ) ~> contents <~ lift(")")).flatMap {
case (s, m) => get.flatMap(
names => put(names.tail).map(_ => (names.head -> s) :: m)
)
}
def contents: ESP[(String, List[(Char, String)])] =
lift("\\d+".r ^^ (_ -> Nil)) | rep1(paren).map(
ps => ps.map(_.head._1).mkString -> ps.flatten
)
def parse(s: String, names: Stream[Char]) =
parseAll(paren.eval(names), s).map(_.toMap)
}
这是可行的,而且它并不比可变状态版本或秒差距版本简洁多少。
But my ExtraStateParsers
丑陋如罪——我不想再考验你的耐心了,所以我不会把它包括在这里(尽管这是一个链接 https://gist.github.com/3747234,如果你真的想要的话)。我不得不为每一个都编写新版本Parser
and Parsers
我上面使用的方法
为了我的ExtraStateParsers
and ESP
types (rep1
, ~>
, <~
, and |
,以防你在数)。如果我需要使用其他组合器,我还必须编写它们的新状态转换器级版本。
有没有更干净的方法来做到这一点?我很想看到 Scalaz 7 的状态 monad 转换器用于通过解析器对状态进行线程化的示例,但 Scalaz 6 或 Haskell 的示例也很有用且值得赞赏。