When to use scala triple caret (^^^) vs double car

2020-03-19 03:05发布

Can someone explain how and when to use the triple caret ^^^ (vs the double caret ^^) when designing scala parser combinators? And also when / how to use the parser.into() method (>>).

1条回答
一纸荒年 Trace。
2楼-- · 2020-03-19 03:36

I'll begin with an example using Scala's Option type, which is similar in some important ways to Parser, but can be easier to reason about. Suppose we have the following two values:

val fullBox: Option[String] = Some("13")
val emptyBox: Option[String] = None

Option is monadic, which means (in part) that we can map a function over its contents:

scala> fullBox.map(_.length)
res0: Option[Int] = Some(2)

scala> emptyBox.map(_.length)
res1: Option[Int] = None

It's not uncommon to care only about whether the Option is full or not, in which case we can use map with a function that ignores its argument:

scala> fullBox.map(_ => "Has a value!")
res2: Option[String] = Some(Has a value!)

scala> emptyBox.map(_ => "Has a value!")
res3: Option[String] = None

The fact that Option is monadic also means that we can apply to an Option[A] a function that takes an A and returns an Option[B] and get an Option[B]. For this example I'll use a function that attempts to parse a string into an integer:

def parseIntString(s: String): Option[Int] = try Some(s.toInt) catch {
  case _: Throwable => None
}

Now we can write the following:

scala> fullBox.flatMap(parseIntString)
res4: Option[Int] = Some(13)

scala> emptyBox.flatMap(parseIntString)
res5: Option[Int] = None

scala> Some("not an integer").flatMap(parseIntString)
res6: Option[Int] = None

This is all relevant to your question because Parser is also monadic, and it has map and flatMap methods that work in very similar ways to the ones on Option. It also has a bunch of confusing operators (which I've ranted about before), including the ones you mention, and these operators are just aliases for map and flatMap:

(parser ^^ transformation) == parser.map(transformation)
(parser ^^^ replacement) == parser.map(_ => replacement)
(parser >> nextStep) == parser.flatMap(nextStep)

So for example you could write the following:

object MyParser extends RegexParsers {
  def parseIntString(s: String) = try success(s.toInt) catch {
    case t: Throwable => err(t.getMessage)
  }

  val digits: Parser[String] = """\d+""".r
  val numberOfDigits: Parser[Int] = digits ^^ (_.length)
  val ifDigitsMessage: Parser[String] = digits ^^^ "Has a value!"
  val integer: Parser[Int] = digits >> parseIntString
}

Where each parser behaves in a way that's equivalent to one of the Option examples above.

查看更多
登录 后发表回答