它是IORef的正确用法吗?

时间:2015-05-30 12:22:36

标签: scala io functional-programming scalaz ioref

目标是要求人输入一个整数,并验证它确实是一个整数。如果不是 - 那就再问一遍。 第一次尝试是使用 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)

那么,我在这里滥用参考概念吗?为什么不起作用?

由于

更新 实际上我通过编写这个方法解决了最初的问题:

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。

更新2:我的耻辱,Monad的iterateWhile确实夸大了这一点:)

1 个答案:

答案 0 :(得分:1)

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代表一个可变引用(功能编程试图避免),它应该非常谨慎地使用。通过首先尝试编写纯函数来解决问题总是一个好主意。