将Monad包装到解析器中 - 我是否需要Monad变换器以及如何操作?

时间:2013-06-22 10:47:57

标签: scala scalaz monad-transformers

我有一个monadic类型Exp,我想构建一个解析为这样一个值的解析器。以下代码有效,但是我能做些更好/更酷的事情吗?

def grid(x: Int, y: Int): Problem = ???
def expInt: Parser[Exp[Int]] = ???

def grid: Parser[Exp[Problem]] =
  for{
    ex ~ _ ~ ey <- "grid(" ~> expInt ~ "," ~ expInt <~ ")"
  } yield for{
    x <- ex
    y <- ey
  } yield grid(x,y)

我听说过monad变形金刚,但仍然有点害怕Scalaz 7的奇怪的导入系统。有人可以回答是否

  1. 我可以使用monad变换器和
  2. 原则上看起来如何,使用Scalaz 7包装我自己的Exp Monad的Scala组合器解析器。

1 个答案:

答案 0 :(得分:6)

首先是简单的部分 - “奇怪的”导入系统:

import scalaz._, Scalaz._

这就是全部。这将永远有效(当然,除非是名称冲突,但这对任何库来说都是可能的并且易于解决),没有人会因为没有使用新的单点菜单导入而瞧不起你,这主要是关于文档的。

monad变换器的基本模式是你有一个WhateverT[F[_], ...]类型类,它有一个名为runWhateverT(或只是run)的方法,它返回F[Whatever[...]]。< / p>

在这种情况下,您似乎希望得到Parser[Exp],这表明您需要自己的ExpT[F[_]]转换器。我不会从有关如何实现它的细节开始(当然,这将取决于你的monad的语义),但是将使用Scalaz的OptionT给出一个例子:

import scala.util.parsing.combinator._
import scalaz._, Scalaz._

object MyParser extends RegexParsers {
  implicit val monad = parserMonad(this)

  def oddNumber: OptionT[Parser, Int] = OptionT.optionT(
    "\\d+".r ^^ (_.toInt) ^^ (i => (i % 2 != 0) option i)
  )

  def pairOfOdds: OptionT[Parser, (Int, Int)] = for {
    _ <- literal("(").liftM[OptionT]
    x <- oddNumber
    _ <- literal(",").liftM[OptionT]
    y <- oddNumber
    _ <- literal(")").liftM[OptionT]
  } yield (x, y)

  def apply(s: String) = parse(pairOfOdds.run, s)
}

有关我们需要implicit val monad = ...行的原因的讨论,请参阅my question here

它的工作原理如下:

scala> MyParser("(13, 43)")
res0: MyParser.ParseResult[Option[(Int, Int)]] = [1.9] parsed: Some((13,43))

scala> MyParser("(13, 42)")
res1: MyParser.ParseResult[Option[(Int, Int)]] = [1.8] parsed: None

请注意,oddNumber.run会为我们提供一个Parser[Option[Int]]来解析一串数字并返回Some(i)(如果它们代表奇数)或None (如果他们是偶数)。

我们实际上并没有打电话给oddNumber.run,但在这种情况下 - 我们使用monad变换器意味着我们可以用oddNumber组合Parser单个literal(...)理解中的动作(for此处)。

这里的语法很丑陋 - 例如,我们失去了很好的~组合器以及从StringParser[String]的隐式转换,但是我们可以很容易地编写这些内容的提升版本我们想要。