Pattern matching LazyList

One thing I liked about Stream is that I could use pattern matching that was very similar to List. With LazyList, it’s trickier because of the lack of a LazyList.Empty object. Of course, I can write this:

def getAt[A](s: LazyList[A], i: Int): A = (s, i) match {
  case (h #:: _, 0) => h
  case (_ #:: t, _) => getAt(t, i - 1)
  case _ => throw new NoSuchElementException("getAt(empty)")
}

but I hate to have to reorder my patterns because there’s no Empty to match on.

I can also do this:

val LazyNil = LazyList.empty

def getAt[A](s: LazyList[A], i: Int): A = (s, i) match {
  case (LazyNil, _) => throw new NoSuchElementException("getAt(empty)")
  case (h #:: _, 0) => h
  case (_ #:: t, _) => getAt(t, i - 1)
}

Is there anything wrong with this? Is there a good reason LazyList does not define such a val? Will they add one if I ask nicely?

An alternative is

case (l, _) if l.isEmpty =>

Looking at the source, I see there is a private[this] val empty_ that seems to be just what you want, but I’m not sure why it’s not exposed. LazyList is evaluated lazily, so it’s possible that they don’t want to give the wrong impression that matching against an empty instance is necessarily cheap.

If there is no good reason why it’s not exposed, exposing it can, at the earliest, be done in 2.14, so your own private helper sounds like a good idea.

1 Like

you don’t need a guard, you can do this:

case (LazyList(), _) =>

3 Likes

It’s the cached instance that’s returned by LazyList.empty - you can just call that. It’s not exposed because it’s an implementation detail and there’s no reason to expose it.

Also it’s worth noting that because pattern matching against non-extractors uses ==, you can also just use Nil if you want. Seqs are not required to be the same type to be equal, they just need to have the same elements in the same order.

This is also a good solution.

On my code example, pattern matching on Nil doesn’t work:

error: pattern type is incompatible with expected type;
found   : scala.collection.immutable.Nil.type
required: LazyList[A]

My reason for exposing the empty object is to do pattern patching (and to keep stream code as similar to list code as possible). Any reason not to expose it?

I can not really think of such a reason, actually. We could probably make it public. In the meantime, I believe case LazyList() => is the simplest workaround.

gah, sorry. my mistake.

it’s already exposed as LazyList.empty though, so you can just use that. if you had a val LazyNil inside the LazyList companion, using it would be a method invocation that returns a backing field, which is exactly what LazyList.empty already does. to my knowledge, the fact that it’s parameterized is irrelevant in terms of usage or performance on the JVM

The problem with LazyList.empty is that it’s a def (it must be, in order to take a type parameter), so it isn’t a stable identifier, which means it can’t be used in a pattern:

scala 2.13.1> LazyList() match { case LazyList.empty => "yes" }
                                               ^
              error: stable identifier required, but scala.`package`.LazyList.empty found.
2 Likes

Note also that anyway, other collection types don’t support this kind of matching either, and for the same reason (empty exists but is a def):

scala 2.13.4> Nil match { case List.empty => "yes" }
                                    ^
              error: stable identifier required, but scala.`package`.List.empty found.

So LazyList is the same as the other collection types. It’s List which is unusual and an outlier for exposing Nil . I don’t see an argument for adding more such outliers.

I know it’s an old discussion, but I just realized that compiling this:

  def f[A](list: LazyList[A]): String = list match {
    case LazyList() => "empty"
    case _ #:: _    => "nonempty"
  }

with -Xlint gives me a nasty warning:

warning: match may not be exhaustive.
It would fail on the following input: (x: scala.collection.immutable.LazyList[?] forSome x not in Nil)
  def f[A](list: LazyList[A]): String = list match {

That doesn’t seem to make sense.

1 Like

re matching LazyList in 2.13.4, see https://github.com/scala/bug/issues/12243

Ideally, I think, the following should and could be made to work without warning as a general stratagy.

def f[A](list: LazyList[A]): String = list match {
  case LazyList()      => "empty"
  case LazyList(_, _*) => "nonempty"
}

Unfortunately, it doesn’t (yet).

Other variations will need to be special cased in the compiler. It seems Dale has a mind to do just that.