尝试从io monad中从爪哇扫描仪阅读时,无限循环

发布于 2025-02-04 11:18:57 字数 1435 浏览 4 评论 0原文

我试图为猫效应课程做一个练习,其中要求我逐行阅读文档,并等待两行之间的一定时间。

老师的解决方案如下:

def readLines(sc: Scanner): IO[Unit] =
  if sc.hasNextLine then IO(sc.nextLine).debug *> IO.sleep(1.second) *> readLines(sc)
  else IO.unit

但是,我试图从上面的同一功能创建一个尾部回复功能,看起来像这样:

def readLines(sc: Scanner): IO[Unit] =

  @tailrec
  def tailReader(accum: IO[Unit]): IO[Unit] =

    if sc.hasNextLine then tailReader(accum *> IO(sc.nextLine).debug *> IO.sleep(1.second))
    else accum

  tailReader(IO.unit)

end readLines

问题是,第一个实现确实是按行读取行并完成的。另一方面,我的实现从未完成,甚至从未读过文档的第一行,然后是OutofMemoryError。

我的问题是,有什么办法可以以一种尾巴回报和纯粹功能性的方式实施第一个功能?

我知道有一种方法可以通过使用>>运算符而不是*>一个来制作第一个实现堆栈安全。但是,这不是我要寻找的,而是我希望能够使用@TailRec注释。

我能够部分解决该错误的方式是在功能开头创建一个值,该值将当前行从扫描仪中保存到扫描仪,这样解决了问题:

def readLines(sc: Scanner): IO[Unit] =

  @tailrec
  def tailReader(accum: IO[Unit]): IO[Unit] =

    val currentLine = sc.nextLine

    if sc.hasNextLine then tailReader(accum *> IO(currentLine).debug *> IO.sleep(1.second))
    else accum >> IO(currentLine).debug.void

  tailReader(IO.unit)

end readLines

尽管这解决了问题,但我很确定该解决方案不是纯粹的功能。

如果您能给我解决这个问题,我将非常感激,我才开始了解效果世界,您的帮助将派上用场!

对于其他一切,祝您有美好的一天。

I was trying to do an exercise for a Cats Effect course in which I was required, among other things, to read a document line by line and wait a certain amount of time between lines.

The teacher's solution was the following:

def readLines(sc: Scanner): IO[Unit] =
  if sc.hasNextLine then IO(sc.nextLine).debug *> IO.sleep(1.second) *> readLines(sc)
  else IO.unit

However, I attempted to create a tail-recursive function from the same function above, and it looked something like this:

def readLines(sc: Scanner): IO[Unit] =

  @tailrec
  def tailReader(accum: IO[Unit]): IO[Unit] =

    if sc.hasNextLine then tailReader(accum *> IO(sc.nextLine).debug *> IO.sleep(1.second))
    else accum

  tailReader(IO.unit)

end readLines

The problem is, the first implementation does read line by line and finishes. On the other hand, my implementation never finishes and never reads even the first line of the document, and is then followed by an OutOfMemoryError.

My question is, is there any way to implement the first function in a way that is tail-recursive and purely functional?

I know there is a way to make the first implementation stack-safe by using the >> operator instead of the *> one. However that's not what I'm looking for, instead I'm looking to be able to use the @tailrec annotation.

The way I was able to partially fix the error was to create a value at the beginning of the function that saves the current line from the scanner, like this:

def readLines(sc: Scanner): IO[Unit] =

  @tailrec
  def tailReader(accum: IO[Unit]): IO[Unit] =

    val currentLine = sc.nextLine

    if sc.hasNextLine then tailReader(accum *> IO(currentLine).debug *> IO.sleep(1.second))
    else accum >> IO(currentLine).debug.void

  tailReader(IO.unit)

end readLines

Although this solved the problem, I am pretty sure that the solution is not purely functional.

I would be very grateful if you could give me a hand with this problem, I am just starting to understand the world of effects, and your help would come in handy!

For everything else, have a really nice day.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

眼泪淡了忧伤 2025-02-11 11:18:57

一些澄清:

  1. 关于>>*>
  def *>[B](that: IO[B]): IO[B] =
    productR(that)

  def productR[B](that: IO[B]): IO[B] =
    flatMap(_ => that)

  def >>[B](that: => IO[B]): IO[B] =
    flatMap(_ => that)
  1. 您可以使用flatmap进行堆栈安全递归,因为 monad 是在猫中设计的。

我将在以下示例中与flatmap交换>>*>因为已经保证了堆栈安全。

现在,这里有什么问题

def readLines(sc: Scanner): IO[Unit] = {

  def tailReader(accum: IO[Unit]): IO[Unit] = {
    if (sc.hasNextLine)
      tailReader { 
        accum
          .flatMap(_ => IO(sc.nextLine()))
          .flatMap(_ => IO.sleep(1.second))
      }
    else
      tailReader(accum)
  }

  tailReader(IO.unit)
}

我们正在检查sc.hasnextline,而未评估sc.nextline
IO(sc.nextline)在这一点上只是一个描述,什么都没评估。

使flatmap链的递归调用部分解决此问题:

  def tailReader(accum: IO[Unit]): IO[Unit] = {
    if (sc.hasNextLine)
      IO(sc.nextLine())
        .flatMap(_ => IO.sleep(1.second))
        .flatMap(_ => tailReader(accum)) //make the call here
    else
      tailReader(accum)
  }

然后,递归方法还有另一个小问题。
tailreader(..)仅在检查下一行后才返回IO。

使用io.suspend与评估完全分开的定义。

   def tailReader(accum: IO[Unit]): IO[Unit] = IO.suspend {
    if (sc.hasNextLine)
      IO(sc.nextLine())
        .flatMap(_ => IO.sleep(1.second))
        .flatMap(_ => accum)
    else
      tailReader(accum)
  }

现在tailReader(io.unit)甚至一次返回IO,而无需调用hasnextline甚至一次。

Some clarifications:

  1. Regarding >> and *>:
  def *>[B](that: IO[B]): IO[B] =
    productR(that)

  def productR[B](that: IO[B]): IO[B] =
    flatMap(_ => that)

  def >>[B](that: => IO[B]): IO[B] =
    flatMap(_ => that)
  1. You can do stack safe recursion with flatMap because of how Monad is designed in Cats.

I'll swap >> and *> with flatMap in the following examples and drop @tailrec as stack safety is already guaranteed.

Now what's wrong here

def readLines(sc: Scanner): IO[Unit] = {

  def tailReader(accum: IO[Unit]): IO[Unit] = {
    if (sc.hasNextLine)
      tailReader { 
        accum
          .flatMap(_ => IO(sc.nextLine()))
          .flatMap(_ => IO.sleep(1.second))
      }
    else
      tailReader(accum)
  }

  tailReader(IO.unit)
}

We're checking sc.hasNextLine continually while not evaluating sc.nextLine.
IO(sc.nextLine) is only a description at this point, nothing is evaluated.

Make the recursive call part of the flatMap chain to solve this:

  def tailReader(accum: IO[Unit]): IO[Unit] = {
    if (sc.hasNextLine)
      IO(sc.nextLine())
        .flatMap(_ => IO.sleep(1.second))
        .flatMap(_ => tailReader(accum)) //make the call here
    else
      tailReader(accum)
  }

then there's another small issue with the recursive method.
tailReader(..) returns an IO only after it checks for next line once.

Use IO.suspend to completely separate definition from evaluation.

   def tailReader(accum: IO[Unit]): IO[Unit] = IO.suspend {
    if (sc.hasNextLine)
      IO(sc.nextLine())
        .flatMap(_ => IO.sleep(1.second))
        .flatMap(_ => accum)
    else
      tailReader(accum)
  }

Now tailReader(IO.unit) returns the IO without calling hasNextLine even once.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文