Понимать поведение преобразований scala с чередованием потоков - PullRequest
0 голосов
/ 13 мая 2018

Я читаю и получаю удовольствие от примеров и упражнений, содержащихся в книге Функциональное программирование в Scala . Я изучаю главу о строгости и лени о потоке .

Я не могу понять вывод, полученный в следующем фрагменте кода:

sealed trait Stream[+A]{

  def foldRight[B](z: => B)(f: (A, => B) => B): B =   
  this match {
    case Cons(h,t) => f(h(), t().foldRight(z)(f))   
    case _ => z
  }

  def map[B](f: A => B): Stream[B] = foldRight(Stream.empty[B])((h,t) => {println(s"map h:$h"); Stream.cons(f(h), t)})

  def filter(f:A=>Boolean):Stream[A] = foldRight(Stream.empty[A])((h,t) => {println(s"filter h:$h"); if(f(h)) Stream.cons(h,t) else t})
}

case object Empty extends Stream[Nothing]

case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]   

object Stream {
  def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = {   
    lazy val head = hd   
    lazy val tail = tl
    Cons(() => head, () => tail)
  }
  def empty[A]: Stream[A] = Empty   

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

Stream(1,2,3,4,5,6).map(_+10).filter(_%2==0)

Когда я выполняю этот код, я получаю этот вывод:

map h:1
filter h:11
map h:2
filter h:12

Мои вопросы:

  1. Почему выходные данные карты и фильтра чередуются ?
  2. Не могли бы вы объяснить все шаги от создания потока до последнего шага для получения этого поведения?
  3. Где находятся другие элементы списка, которые также проходят фильтрацию преобразования , поэтому 4 и 6?

1 Ответ

0 голосов
/ 15 мая 2018

Ключ к пониманию этого поведения, я думаю, заключается в подписи foldRight.

def foldRight[B](z: => B)(f: (A, => B) => B): B = ...

Обратите внимание, что второй аргумент, f, является функцией, которая принимает два параметра: A и псевдоним (ленивый) B. Уберите эту лень, f: (A, B) => B, и вы не только получите ожидаемую группировку методов (все шаги map() перед всеми filter() шагами), они также идут в обратном порядке: сначала обрабатывается 6, а 1 обрабатывается последним, как и следовало ожидать от foldRight.

Как один маленький => выполняет всю эту магию? В основном это говорит о том, что 2-й аргумент f() будет храниться в резерве, пока он не потребуется.

Итак, пытаясь ответить на ваши вопросы.

  1. Почему чередуются выходные данные карты и фильтра?

Поскольку каждый вызов map() и filter() задерживается до момента, когда запрашиваются значения.

  1. Не могли бы вы объяснить все этапы создания потока до последнего шага для получения этого поведения?

Не совсем. Это заняло бы больше времени и ТАКОГО места для ответов, чем я готов внести, но давайте сделаем всего несколько шагов в болоте.

Мы начинаем с Stream, который выглядит как серия Cons, каждая из которых содержит Int и ссылку на следующий Cons, но это не совсем точно. Каждый Cons действительно содержит две функции, при вызове 1-й производит Int, а 2-й производит следующий Cons.

Позвоните map() и передайте ему функцию "+10". map() создает новую функцию: «Учитывая h и t (оба значения), создайте new Cons. Функция заголовка нового Cons при вызове будет функция "+10" применяется к текущему значению напора. Новая функция хвоста будет выдавать значение t в полученном виде. " Эта новая функция передается в foldRight.

foldRight получает новую функцию, но оценка второго параметра функции будет отложена до тех пор, пока она не потребуется. h() вызывается для получения текущего значения заголовка, t() вызывается для извлечения текущего значения хвоста, и для него вызывается рекурсивный вызов foldRight.

Вызовите filter() и передайте ему функцию isEven. filter() создает новую функцию: «Дано h и t, создайте new Cons , если h пройдет тест isEven. Если нет, верните t «. Это настоящий t. Не обещаю оценить его значение позже.

  1. Где находятся другие элементы списка, которые также проходят преобразование фильтра, так что 4 и 6?

Они все еще там, ожидая оценки. Мы можем форсировать эту оценку, используя сопоставление с образцом для извлечения различных Cons один за другим.

val c0@Cons(_,_) = Stream(1,2,3,4,5,6).map(_+10).filter(_%2==0)
//  **STDOUT**
//map h:1
//filter h:11
//map h:2
//filter h:12

c0.h()  //res0: Int = 12

val c1@Cons(_,_) = c0.t()
//  **STDOUT**
//map h:3
//filter h:13
//map h:4
//filter h:14

c1.h()  //res1: Int = 14

val c2@Cons(_,_) = c1.t()
//  **STDOUT**
//map h:5
//filter h:15
//map h:6
//filter h:16

c2.h()  //res2: Int = 16
c2.t()  //res3: Stream[Int] = Empty
...