通过Scala中的解析器来线程化额外状态

前端之家收集整理的这篇文章主要介绍了通过Scala中的解析器来线程化额外状态前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我会给你的tl; dr前面

我试图使用状态monad变压器在Scalaz 7通过一个解析器线程额外的状态,我有麻烦做任何有用的没有写很多t m – > 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"
)

在给定级别可能有一个数字串或任意多个子表达式,但这两种内容不会混合在单个括号表达式中。

为了保持简单,我们假设名称流将永远不会
包含重复或数字,并且它将总是包含足够的
我们的输入的名称

使用具有一点可变状态的解析器组合器

上面的例子是一个稍微简化的解析问题的版本
this Stack Overflow question
I answered that question
一个看起来大致如下的解决方案:

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)
}

这不是太糟糕了,但我宁愿避免可变状态。

我想要的是

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)))"

这是一个相当直接的翻译我上面的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,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)
}

这工作,它不是那么简单的可变状态版本或Parsec版本。

但我的ExtraStateParsers是丑陋的罪 – 我不想尝试你的耐心比我已经有,所以我不会包括在这里(虽然here’s a link,如果你真的想要它)。我不得不写我以上使用的每个解析器和解析器方法的新版本
对于我的ExtraStateParsers和ESP类型(rep1,〜>,<〜和|,以防你计数)。如果我需要使用其他组合器,我不得不写他们的新的状态变压器级版本。 有没有更清洁的方法来做到这一点?我想看看Scalaz 7的状态monad变换器被用来通过解析器线程状态的一个例子,但Scalaz 6或Haskell的例子也将是有用的和赞赏。

解决方法

也许最一般的解决方案是重写Scala的解析器库以适应单子计算,而解析(就像你一样),但这将是一个相当费力的任务。

我建议使用ScalaZState解决方案,其中我们的每个结果不是类型Parse [X]的值,但类型为Parse [State [Stream [Char],X]](别名为ParserS [X])的值。所以总的解析结果不是一个值,而是一个monadic状态值,然后在一些Stream [Char]上运行。这几乎是一个monad变压器,但我们必须手动提升/ unlifting。它使代码有点丑,因为我们需要提升值有时或使用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,String)])] = digits | parens;
  def digits: ParserS[(String,String)])] =
    "\\d+".r ^^ (_ -> Nil) ^^ (toState _)
  def parens: ParserS[(String,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': _*)));
  }
}

注意:我相信你使用StateT的方法[Parser,Stream [Char],_]在概念上是不正确的。类型说,我们正在构造一个解析器给定一些状态(一个名称流)。因此,有可能给定不同的流,我们得到不同的解析器。这不是我们想要做的。我们只希望解析的结果取决于名称,而不是整个解析器。这样Parser [State [Stream [Char],_]]似乎更合适(Haskell的Parsec采用类似的方法,状态/ monad在解析器中)。

猜你在找的Scala相关文章