When to use scala triple caret (^^^) vs double caret (^^) and the into method (>>)
Asked Answered
A

1

7

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 (>>).

Assassin answered 21/1, 2014 at 13:25 Comment(0)
E
22

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.

Etheline answered 21/1, 2014 at 14:35 Comment(2)
Perfect! Short, succinct, pragmatic and to the point answer! Thanks so much!Assassin
Again, thanks for this concise answer! Although I'm aware that one of Scala's strengths is its conciseness, at least compared to Java, is it fully necessary to use these carets when writing Parsers, or can the developer write 'long-hand' map references (I favour this in my own code, simply for readability)? I assume that the compiler will optimise and generate equivalent code, right?Riven

© 2022 - 2024 — McMap. All rights reserved.