目标是要求用户输入一个整数,并验证它确实是一个整数。如果不是,那么再问一次。第一次尝试是使用ioMonad。whileM,因为它实际上在IO内部返回值,并写一些类似的东西(然后我们可以"安全地"将String强制转换为Int):
val input: IO[Option[String]] = ioMonad.whileM(readLn.map(_ exists notDigit),
askAndReadNumber)(scalaz.std.AllInstances.optionInstance)
但是这种方法没有成功,因为在这个条件中,我不仅验证了值,而且再次从控制台读取了它。
因此,由于我需要读取输入,然后以某种方式将其传递给条件,我在想,IORef可能正是正确的工具(我以前从未使用过它,因此将其视为我学习函数式编程的谦卑尝试)。我最终得到了这个: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
实际上整个循环被完全忽略了,程序直接转到初始ref:
那一行num.flatMap(_.read).map(_.toInt)
那么,我在这里误用了Ref概念吗?为什么不工作?感谢更新:实际上,我通过写这个方法解决了最初的问题:
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))
然后是whileMpropagating(askAndReadNumber)(_ forall notDigit)(ioMonad) map (_.toInt)
但我仍然对在这里使用IORef感兴趣。
Update2:我的耻辱,iterateWhile
在Monad正是这样做的:)
IORef
对您的情况来说是多余的。只需几行代码就可以解决这个问题:
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
表示一个可变引用(函数式编程试图避免),应该非常谨慎地使用它。首先尝试编写纯函数来解决问题总是一个好主意。