I am trying Cats for the first time and am using Scala 3, and I am trying to implement a set of parser combinators for self-pedagogy, however; I am stuck on the definition of the tailRecM
function for Monad. I have managed Functor and Applicative just fine.
I have defined my type in question as a function such that:
type Parser[A] = (input: List[Token]) => ParseResult[A]
with corresponding return types as:
type ParseResult[A] = Success[A] | Failure
case class Success[A](value: A, tokens: List[Token])
case class Failure(msg: String, tokens: List[Token])
My current definition of tailRecM
is as follows:
@annotation.tailrec
def tailRecM[A, B](init: A)(fn: A => Parser[Either[A, B]]): Parser[B] =
(input: List[Token]) =>
fn(init)(input) match {
case f: Failure => f
case s: Success[Either[A, B]] => s.value match {
case Right(b) => Success(b, s.tokens)
case Left(a) => tailRecM(a)(fn) // won't compile
}
}
If I attempt to build I get "Found: Parsing.Parser[B] Required: Parsing.ParseResult[B]"
for tailRecM(a)(fn)
The issue as far as I can tell stems from the fact that my type in question Parser[A]
is a function type and not simply a value type? I attempted to ameliorate the issue by modifying the tailRecM
recursive call to tailRecM(a)(fn)(input)
but then this is obviously not stack safe, and also will not compile.
How can I resolve this issue, and more broadly, how can I implement the Monad typeclass for function types in general?
It's not possible to make tailRecM
itself tail-recursive; you need to define a tail-recursive helper method
Here's how the cats
library implements tailRecM
for Function1
def tailRecM[A, B](a: A)(fn: A => T1 => Either[A, B]): T1 => B =
(t: T1) => {
@tailrec
def step(thisA: A): B =
fn(thisA)(t) match {
case Right(b) => b
case Left(nextA) => step(nextA)
}
step(a)
}
This is because monadic recursion is a form of mutual tail-recursion, where two methods flip back and forth calling each other. The scala compiler can't optimize that. So instead we inline the definition of the monadic part rather than calling flatMap
or another method
You need to pass the input
again to the tailRecM
call
tailRecM(a)(fn)(input)
because tailRecM(a)(fn)
returns a Parser
, but you need the ParserResult
from that returned Parser
, as you already did in all other cases.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.