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