Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy val to implement lazy lists in Scala

I'm trying to learn how to use the built-in laziness in Scala by implementing my own version of lazy lists:

object LazyList {

  def empty[A] : LazyList[A] = new LazyList[A] {
    lazy val uncons = None
  }

  def cons[A](h : => A, t : => LazyList[A]) : LazyList[A] = new LazyList[A] {
    lazy val uncons = Some( (h,t) )
  }

  def from(s : Int) : LazyList[Int] = new LazyList[Int] {
    lazy val uncons = Some( (s,from(s + 1)) )
  }
}

trait LazyList[A] {

  import LazyList._

  def uncons : Option[(A,LazyList[A])]

  def fmap[B](f : A => B) : LazyList[B] = uncons match {
    case None          => empty
    case Some( (h,t) ) => cons(f(h),t.fmap(f))
  }

  def take(i : Int) : LazyList[A] = uncons match {
    case None          => empty
    case Some( (h,t) ) => if (i <= 0) empty else cons(h,t.take(i - 1))
  }

  override def toString : String = uncons match {
    case None          => "[]"
    case Some( (h,t) ) => "[" ++ h.toString ++ ",..]"
  }
}

This seems to work and I can, for instance, map { _ + 2} to the infinite list:

> LazyList from 1 fmap { _ + 2 }
res1: LazyList[Int] = [2,..]

I decided to implement some functions that I usually use like drop, take, etc and I've been able to implement them except for inits. My implementation of inits is:

  def inits : LazyList[LazyList[A]] = uncons match {
    case None          => empty
    case Some( (h,t) ) => cons(empty,t.inits.fmap(cons(h,_)))
  }

The problem is that it doesn't work on infinite lists for some reason. I can't, for example, write:

  > LazyList from 1 inits

because it runs forever. The problem seems to be fmap after t.inits that, for some reason, breaks the laziness (if I remove fmap it is wrong but lazy). Why fmap enforce strictness and, given my type LazyList, how can inits be implemented such that it works on infinite lists?

like image 226
mariop Avatar asked Apr 12 '14 13:04

mariop


People also ask

What is the use of lazy Val in Scala?

Scala provides a nice language feature called lazy val that defers the initialization of a variable. The lazy initialization pattern is common in Java programs. Though it seems tempting, the concrete implementation of lazy val has some subtle issues.

Does Scala have lazy evaluation?

Lazy Evaluation in Scala Haskell is a functional programming language that uses lazy evaluation by default.

What is lazy list in Scala?

This class implements an immutable linked list. We call it "lazy" because it computes its elements only when they are needed. Elements are memoized; that is, the value of each element is computed at most once. Elements are computed in-order and are never skipped.

What is lazy evaluation and lazy Val?

Lazy initialization means that whenever an object creation seems expensive, the lazy keyword can be stick before val. This gives it the advantage to get initialized in the first use i.e. the expression inbound is not evaluated immediately but once on the first access. Example: // Scala program of Lazy val.


1 Answers

Both fmap and inits peel one actual (non-lazy) element when invoked; they both uncons. Since they call each other, the chain never terminates on an infinite LazyList.

Specifically, note that uncons returns not a => LazyList but the actual LazyList, so when you call

Some( (h,t) )

that evaluates t. If the evaluation of t calls an uncons, it too will evaluate and you're off to the stack overflow races. It's just tricky to notice here because it's dual recursion.

You need to make one of them peel zero copies. One way you can do that is by making the second argument of the uncons tuple lazy (explicitly, by making it a Function0 instead):

object LazyList {
  def empty[A]: LazyList[A] = new LazyList[A] {
    lazy val uncons = None
  }

  def cons[A](h: => A, t: => LazyList[A]) : LazyList[A] = new LazyList[A] {
    lazy val uncons = Some( (h,() => t) )
  }

  def from(s: Int): LazyList[Int] = new LazyList[Int] {
    lazy val uncons = Some( (s,() => from(s + 1)) )
  }
}

trait LazyList[A] {
  import LazyList._

  def uncons: Option[(A,() => LazyList[A])]

  def fmap[B](f: A => B): LazyList[B] = uncons match {
    case None          => empty
    case Some( (h,t) ) => cons(f(h),t().fmap(f))
  }

  def take(i: Int): LazyList[A] = uncons match {
    case None          => empty
    case Some( (h,t) ) => if (i <= 0) empty else cons(h,t().take(i - 1))
  }

  override def toString: String = uncons match {
    case None          => "[]"
    case Some( (h,t) ) => "[" ++ h.toString ++ ",..]"
  }
}

Then your implementation works:

  def inits: LazyList[LazyList[A]] = uncons match {
    case None          => empty
    case Some( (h,t) ) => cons(empty,t().inits.fmap(cons(h,_)))
  }

It might be nicer to have an internal uncons that did this and an outward-facing uncons that applies the tail for you.

like image 158
Rex Kerr Avatar answered Oct 15 '22 01:10

Rex Kerr