Функциональное программирование: очень трудно понять, как эта функция лениво оценивает - PullRequest
2 голосов
/ 18 февраля 2020

Я работаю над книгой Functional Programming in Scala и нахожусь в разделе о ленивых оценках. Существует упражнение для реализации функции takeWhile с использованием foldRight. Мне удалось завершить его успешно, но когда я добавил операторы печати, я увидел, что он, похоже, выполняет обработку, чего я не ожидал. Я очень смущен этим.

Вот код:

trait McStream[+A] {
    def uncons: Option[(A, McStream[A])]

    def isEmpty: Boolean = uncons.isEmpty

    def toList: List[A] = {
      uncons match {
        case Some(head -> tail) => head :: tail.toList
        case None => Nil
      }
    }

    def foldRight[B](z: => B)(f: (A, => B) => B): B = {
      uncons match {
        case Some(head -> tail) =>
          println(s"Inside foldRight, head is $head")
          f(head, tail.foldRight(z)(f))
        case None => z
      }
    }

    // TODO how does evaluate?  Trace steps
    // TODO it seems to be storing a deferred takeWhile in the `b` variable that evaluates during the cons
    def takeWhile(p: A => Boolean): McStream[A] = {
      foldRight(McStream.empty[A]) { (a, b) =>
        println(s"a is $a, b is $b")
        if (p(a)) {
          McStream.cons(a, b)
        } else {
          McStream.empty
        }
      }
    }
}

С вспомогательным объектом для конструкторов:

object McStream {
    def empty[A]: McStream[A] = new McStream[A] {
      override def uncons: Option[(A, McStream[A])] = None
    }

    def cons[A](hd: => A, tl: => McStream[A]): McStream[A] = {
      new McStream[A] {
        lazy val uncons = Some((hd, tl))
      }
    }

    def apply[A](as: A*): McStream[A] = {
      if (as.isEmpty) empty
      else cons(as.head, apply(as.tail: _*))
    }
  }
}

Вот тест, который я идущий:

"take while a predicate is matched" in {
      val stream = McStream(1, 2)
      stream.takeWhile(_ < 2).toList shouldEqual List(1)
    }

И вот вывод, который я получаю:

Inside foldRight, head is 1
Inside foldRight, head is 2
a is 2, b is McStream(None)
a is 1, b is McStream(None)
Inside foldRight, head is 2
a is 2, b is McStream(None)

Я запутался в последних двух строках, мне кажется, что он должен повторяться вплоть до конец списка, а затем либо соедините обработанный в данный момент хвост со следующим элементом, если предикат соответствует, либо замените его пустым McStream в противном случае. В этот момент он должен просто возвращать список, а не делать дополнительные foldRight и оценку.

Вот, насколько я понимаю, порядок оценки:

Stream(1, Stream(2, Stream.empty)).takeWhile(_ < 2)
should print Inside foldRight, head is 1
Stream(2, Stream.empty).takeWhile(_ < 2)
should print Inside foldRight, head is 2
Stream.empty.takeWhile(_ < 2)
End of recursion, starts to return
Stream(2, Stream.empty).takeWhile(_ < 2)
should print a is 2, b is Stream.empty
Stream(1, Stream.empty).takeWhile(_ < 2)
should print a is 1, b is Stream.empty
1 #:: Stream.empty

Заранее спасибо!

1 Ответ

4 голосов
/ 18 февраля 2020

Оказывается, что именно те механизмы, которые я использовал, чтобы попытаться понять оценку (утверждения println), вынуждали оценку и вызывали вышеуказанные проблемы. Когда я удаляю операторы печати, он оценивается так, как должен.

Не используйте println в своих ленивых оценках!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...