Is it correct usecase for IORef?

301 Views Asked by At

The goal is to ask person to enter an integer, and verify that it is indeed an integer. If it is not - then ask again. The first attempt was to use ioMonad.whileM, becasue it actually returns value inside IO, and write something like that (then we can "safely" cast String to Int):

 val input: IO[Option[String]] = ioMonad.whileM(readLn.map(_ exists notDigit),
 askAndReadNumber)(scalaz.std.AllInstances.optionInstance)

But that approach did not work out, because in the condition, I am not only validating the value, but also reading it from console once again.

So, since I need to read the input, and then pass it somehow to the condition, I was thinking, that IORef might be exactly the right tool (I never used it before, so consider this as my humble attempt to learn functional programming). I ended up with that:

def requireNumber: IO[Int] = {    
    val numref: IO[IORef[String]] = newIORef("a")
    ioMonad.whileM_(condition(numref), askAndReadNumberToIORef(numref))
    numref.flatMap(_.read).map(_.toInt)
  }

  def condition(num: IO[IORef[String]]): IO[Boolean] = for {
    ref ← num
    enteredNumber ← ref.read
  } yield enteredNumber exists notDigit

  def askAndReadNumberToIORef(num: IO[IORef[String]]): IO[Unit] = for {
    ref ← num
    input ← askAndReadNumber
    _ ← ref.write(input)
  } yield ()

  private def notDigit: (Char) ⇒ Boolean =
    !Character.isDigit(_)

  def askAndReadNumber: IO[String] =
    for {
    _ ← putStrLn("Enter a number please")
    maxN ← readLn
  } yield maxN

And what happening is - actually the whole loop is completely ignored, and the program goes directly to the line with initial ref:

num.flatMap(_.read).map(_.toInt)

So, do I misuse Ref concept here? Why is it not working?

Thanks

Update: Actually I solved the initial problem by writing this method:

def whileMpropagating[M[_], A](f: ⇒ M[A])(p: A ⇒ Boolean)(implicit M: Monad[M]): M[A] =
    M.bind(f)(value ⇒ if (!p(value)) f else whileMpropagating(f)(p))

and then whileMpropagating(askAndReadNumber)(_ forall notDigit)(ioMonad) map (_.toInt)

But still I am interested in utilizing the IORef here.

Update2: My shame, iterateWhile in Monad does exaclty this : )

1

There are 1 best solutions below

0
On

IORef is overkill for your case. This is the solution in few lines of code:

import scala.util.Try
import scalaz.effect.IO
import scalaz.syntax.monad._

private def isInteger(str: String) = Try(Integer.parseInt(str)).isSuccess

val requireNumber: IO[Int] = IO.readLn.iterateUntil(isInteger).map(Integer.parseInt)

IORef represents a mutable reference (which functional programming is trying to avoid), and it should be used very sparingly. It is always a good idea to solve your problem by trying to write pure functions first.