Преобразовать для понимания в LazyList

#scala #lazy-evaluation

#scala #отложенная оценка

Вопрос:

У меня есть

  • список файлов ( filepaths )
  • метод, который считывает и обрабатывает файл ( loadData )
  • метод, который вычисляет статистику по этому файлу

Я хочу читать файлы только тогда, когда нам также нужно вычислить статистику. Строка ниже будет перебирать пути к файлам и возвращает их имя и содержимое файла.

 for((name, path) <- filepaths) yield (name, loadData(path))
  

Возможно ли сделать такое для понимания для LazyList , чтобы loadData часть вычислялась лениво?

Ответ №1:

for comprehension это просто синтаксический сахар для комбинаций flatMap и map функций. Вы можете сделать это, просто используя функцию map:

 def loadData(path: String): String = {
  println(s"launch loadData on $path")
  s"${path}_suffix"
}

val filePaths = Seq("p1" -> "path1", "p2" -> "path2")
val lazyList = filePaths.to(LazyList).map{
  case (name, path) => name -> loadData(path)
}
println(lazyList)
println(lazyList.force)

// output will be:
//LazyList(<not computed>)
//launch loadData on path1
//launch loadData on path2
//LazyList((p1,path1_suffix), (p2,path2_suffix))
  

Здесь мы видим, что loadData вычисляется только тогда, когда необходим следующий элемент.

Комментарии:

1. Было бы более эффективно сделать это: filePaths.to(LazyList)

Ответ №2:

Это определенно возможно.

 for ((name, path) <- filepaths) yield (name, loadData(path))
  

Это просто синтаксический сахар для

 filepaths.map { x =>
  val (name, path) = x
  (name, loadData(path)
}
  

(возможно, это не то, для чего компилятор на самом деле отключает его (я забыл, будет ли несколько функций (одна для извлечения пар и одна для вызова loadData ) или нет))

Поскольку есть map on LazyList , если filepaths есть LazyList , это будет работать.

Тем не менее, это не максимально лениво: получение n -й записи требует оценки (n - 1) -й записи.

Конечно, всякий раз, когда у вас есть Seq[(A, B)] (или Iterable[(A, B)] / Traversable[(A, B)] ), стоит подумать, действительно ли вам нужно Function1[A, B] или Map[A, B] (что можно рассматривать просто как простой способ построения подмножества Function[A, B] ).

 // assuming that filepaths is an `Iterable[(A, B)]`
filepaths.toMap.mapValues(loadData(_))
  

(Обратите внимание, что (_) возможно, можно было бы опустить).

Это будет нестрогим (оно не будет вызываться loadData до тех пор, пока вы не выполните поиск по заданному name ), но это не будет максимально ленивым (нет запоминания: каждый поиск приведет к loadData вызову). Если вы хотите максимальной лени, вам придется реализовать свою собственную memoization.