While reading Functional Programming in Scala by Chiusano and Bjarnason, I encountered the following code in chapter 9, Parser Combinators:
trait Parsers[ParseError, Parser[+_]] { self =>
...
def or[A](s1: Parser[A], s2: Parser[A]): Parser[A]
implicit def string(s: String): Parser[String]
implicit def operators[A](p: Parser[A]) = ParserOps[A](p)
implicit def asStringParser[A](a: A)(implicit f: A => Parser[String]):
ParserOps[String] = ParserOps(f(a))
case class ParserOps[A](p: Parser[A]) {
def |[B>:A](p2: Parser[B]): Parser[B] = self.or(p,p2)
def or[B>:A](p2: => Parser[B]): Parser[B] = self.or(p,p2)
}
}
I understand that if there is a type incompatibility or missing parameters during compilation, the Scala compiler would look for a missing function that converts the non-matching type to the desired type or a variable in scope with the desired type that fits the missing parameter respectively.
If a string occurs in a place that requires a Parser[String]
, the string function in the above trait should be invoked to convert the string to a Parser[String]
.
However, I've difficulties understanding the operators
and asStringParser
functions. These are the questions that I have:
case class
and why can't the |
or or
function be defined in the Parsers trait itself?asStringParser
trying to accomplish? What is its purpose here?self
needed? The book says, "Use self to explicitly disambiguate reference to the or method on the trait," but what does it mean?I'm truly enjoying the book but the use of advanced language-specific constructs in this chapter is hindering my progress. It would be of great help if you can explain to me how this code works. I understand that the goal is to make the library "nicer" to use through operators like |
and or
, but don't understand how this is done.
ParserOps[A]
. You don't have to write it out explicitly, because in this case it can be inferred automatically.ParserOps.apply
-factory method in the companion object. You need fewer val
s in the constructor, and you don't need the new
keyword to instantiate ParserOps
. It is not used in pattern matching though, so, you could do the same thing with an ordinary (non-case
) class, wouldn't matter.|
and or
to Parser
, without forcing Parser
to inherit from anything. In this way, you can later declare Parser
to be something like ParserState => Result[A]
, but you will still have methods |
and or
available (even though Function1[ParserState, Result[A]]
does not have them).You could put |
and or
directly in Parsers
, but then you would have to use the syntax
|(a, b)
or(a, b)
instead of the much nicer
a | b
a or b
There are no "real operators" in Scala, everything is a method. If you want to implement a method that behaves as if it were an infix operator, you do exactly what is done in the book.