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 : )
IORef
is overkill for your case. This is the solution in few lines of code: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.