Making a safe RPN calculator 

LYAHFGG:

When we were solving the problem of implementing a RPN calculator, we noted that it worked fine as long as the input that it got made sense.

We have not covered the chapter on RPN calculator, so let’s translate it into Scala.

scala> def foldingFunction(list: List[Double], next: String): List[Double] =
         (list, next) match {
           case (x :: y :: ys, "*") => (y * x) :: ys
           case (x :: y :: ys, "+") => (y + x) :: ys
           case (x :: y :: ys, "-") => (y - x) :: ys
           case (xs, numString) => numString.toInt :: xs
         }
foldingFunction: (list: List[Double], next: String)List[Double]

scala> def solveRPN(s: String): Double =
         (s.split(' ').toList.
         foldLeft(Nil: List[Double]) {foldingFunction}).head
solveRPN: (s: String)Double

scala> solveRPN("10 4 3 + 2 * -")
res0: Double = -4.0

Looks like it’s working.

The next step is to change the folding function to handle errors gracefully. We can implement parseInt as follows:

scala> import scala.util.Try
import scala.util.Try

scala> def parseInt(x: String): Option[Int] =
         (scala.util.Try(x.toInt) map { Some(_) }
         recover { case _: NumberFormatException => None }).get
parseInt: (x: String)Option[Int]

scala> parseInt("1")
res1: Option[Int] = Some(1)

scala> parseInt("foo")
res2: Option[Int] = None

Here’s the updated folding function:

scala> import cats._, cats.data._, cats.implicits._
import cats._
import cats.data._
import cats.implicits._

scala> def foldingFunction(list: List[Double], next: String): Option[List[Double]] =
         (list, next) match {
           case (x :: y :: ys, "*") => ((y * x) :: ys).some
           case (x :: y :: ys, "+") => ((y + x) :: ys).some
           case (x :: y :: ys, "-") => ((y - x) :: ys).some
           case (xs, numString) => parseInt(numString) map {_ :: xs}
         }
foldingFunction: (list: List[Double], next: String)Option[List[Double]]

scala> foldingFunction(List(3, 2), "*")
res3: Option[List[Double]] = Some(List(6.0))

scala> foldingFunction(Nil, "*")
res4: Option[List[Double]] = None

scala> foldingFunction(Nil, "wawa")
res5: Option[List[Double]] = None

Finally, here’s the updated solveRPN using foldM:

scala> def solveRPN(s: String): Option[Double] =
         for {
           List(x) <- (Foldable[List].foldM(s.split(' ').toList, Nil: List[Double]) {foldingFunction})
         } yield x
solveRPN: (s: String)Option[Double]

scala> solveRPN("1 2 * 4 +")
res6: Option[Double] = Some(6.0)

scala> solveRPN("1 2 * 4")
res7: Option[Double] = None

scala> solveRPN("1 8 garbage")
res8: Option[Double] = None