目標は、人に整数を入力してもらい、それが実際に整数であることを確認することです。そうでない場合は、もう一度尋ねてください。最初の試みは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
そして何が起こっているのか - 実際にはループ全体が完全に無視され、プログラムは最初の参照のある行に直接進みます:
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
、モナドではこれを正確に行います:)