Tra*_*own 26 monads haskell scala monad-transformers scalaz
我想使用的状态单子转换Scalaz 7通过一个分析器,线程额外的状态,我无法做任何有用的事情而无需编写大量的t m a -> t m b版本m a -> m b的方法.
假设我有一个包含嵌套括号的字符串,其中包含数字:
val input = "((617)((0)(32)))"
Run Code Online (Sandbox Code Playgroud)
我还有一个新的变量名称流(在这种情况下是字符):
val names = Stream('a' to 'z': _*)
Run Code Online (Sandbox Code Playgroud)
我想从流的顶部拉出一个名称,并在解析它时将其分配给每个括号表达式,然后将该名称映射到表示括号内容的字符串,并将嵌套的括号表达式(如果有)替换为他们的名字.
为了使这更具体,这就是我希望输出看起来像上面的示例输入:
val target = Map(
'a' -> "617",
'b' -> "0",
'c' -> "32",
'd' -> "bc",
'e' -> "ad"
)
Run Code Online (Sandbox Code Playgroud)
在给定级别可能存在一串数字或任意多个子表达式,但这两种内容不会在单个括号表达式中混合.
为了简单起见,我们假设名称流永远不会包含重复项或数字,并且它总是包含足够的输入名称.
上面的示例是此Stack Overflow问题中解析问题的略微简化版本 .我用一个大致如下的解决方案回答了这个问题:
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)
}
Run Code Online (Sandbox Code Playgroud)
这不是太糟糕,但我宁愿避免可变状态.
Haskell的Parsec库使得向解析器添加用户状态变得非常简单:
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)))"
Run Code Online (Sandbox Code Playgroud)
这是我上面的Scala解析器的相当直接的翻译,但没有可变状态.
我正在尝试尽可能接近Parsec解决方案,因为我可以使用Scalaz的状态monad变换器,所以而不是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)
}
Run Code Online (Sandbox Code Playgroud)
这可行,并且它比可变状态版本或Parsec版本简洁得多.
但我ExtraStateParsers作为罪恶是丑陋的 - 我不想比你已经拥有更多的耐心,所以我不会在这里包括它(虽然这里有一个链接,如果你真的想要它).我必须为我的和类型(,和,以及,如果你在计算)编写我上面使用的每个Parser和Parsers方法的新版本.如果我需要使用其他组合器,我也必须编写新的状态变换器级版本.ExtraStateParsersESPrep1~><~|
有更清洁的方法吗?我很想看到一个Scalaz 7的状态monad变换器用于通过解析器进行线程状态的示例,但Scalaz 6或Haskell示例也很有用并且值得赞赏.
Pet*_*lák 11
可能最通用的解决方案是重写Scala的解析器库以在解析时适应monadic计算(就像你部分一样),但这将是一项非常费力的任务.
我建议使用ScalaZ的状态解决方案,其中我们的每个结果都不是类型Parse[X]的值,而是类型的值Parse[State[Stream[Char],X]](别名为ParserS[X]).因此,整体解析结果不是值,而是一个monadic状态值,然后在某些值上运行Stream[Char].这几乎是一个monad变压器,但我们必须手动提升/解除.它使代码有点丑陋,因为我们需要有时提升值或在几个地方使用map/ flatMap,但我相信它仍然是合理的.
import scala.util.parsing.combinator._
import scalaz._
import Scalaz._
import Traverse._
object ParenParser extends RegexParsers with States {
type S[X] = State[Stream[Char],X];
type ParserS[X] = Parser[S[X]];
// Haskell's `return` for States
def toState[S,X](x: X): State[S,X] = gets(_ => x)
// Haskell's `mapM` for State
def mapM[S,X](l: List[State[S,X]]): State[S,List[X]] =
l.traverse[({type L[Y] = State[S,Y]})#L,X](identity _);
// .................................................
// Read the next character from the stream inside the state
// and update the state to the stream's tail.
def next: S[Char] = state(s => (s.tail, s.head));
def paren: ParserS[List[(Char, String)]] =
"(" ~> contents <~ ")" ^^ (_ flatMap {
case (s, m) => next map (v => (v -> s) :: m)
})
def contents: ParserS[(String, List[(Char, String)])] = digits | parens;
def digits: ParserS[(String, List[(Char, String)])] =
"\\d+".r ^^ (_ -> Nil) ^^ (toState _)
def parens: ParserS[(String, List[(Char, String)])] =
rep1(paren) ^^ (mapM _) ^^ (_.map(
ps => ps.map(_.head._1).mkString -> ps.flatten
))
def parse(s: String): ParseResult[S[Map[Char,String]]] =
parseAll(paren, s).map(_.map(_.toMap))
def parse(s: String, names: Stream[Char]): ParseResult[Map[Char,String]] =
parse(s).map(_ ! names);
}
object ParenParserTest extends App {
{
println(ParenParser.parse("((617)((0)(32)))", Stream('a' to 'z': _*)));
}
}
Run Code Online (Sandbox Code Playgroud)
注意:我认为您的方法在StateT[Parser, Stream[Char], _]概念上并不正确.该类型表示我们正在构建一个给定某种状态(名称流)的解析器.因此,给定不同的流可能会得到不同的解析器.这不是我们想要做的.我们只希望解析的结果取决于名称,而不是整个解析器.这种方式Parser[State[Stream[Char],_]]似乎更合适(Haskell的Parsec采用类似的方法,state/monad在解析器中).