Doesn't the lazy Scala keyword provide any 'rated at most once' guarantees in a multi-threaded package?
Yes, it is, as others have stated.
Is the pattern used in real tail implementation an idiomatic way for thread safe lazy evaluation in Scala?
Edit:
I think I have a real answer, why not lazy val . Stream has public API methods like hasDefinitionSize inherited from TraversableOnce . To find out if a Stream has a finite size of not, we need a way to check without materializing the base Stream tail. Since lazy val does not actually reveal the base bit, we cannot do this.
It is supported by SI-1220
To reinforce this point, @ Jasper-M points out that the new LazyList api in strawman (Scala 2.13 collection makeover) no longer has this problem, since the entire collection hierarchy has been redesigned and there are no more such problems.
Performance Issues
I would say "it depends" on which corner you are looking at this problem. From a LOB perspective, I would definitely say go with lazy val for brevity and clarity of implementation. But, if you look at it from the point of view of the author of the Scala collection library, everything starts to look different. Think of it this way, you are creating a library that could potentially be used by many people and run on many machines around the world. This means that you should think about the memory overhead of each structure, especially if you yourself are creating such an important data structure.
I say this because when you use lazy val , by design, you create an extra Boolean field that indicates if the value has been initialized, and I assume that this is what the library authors sought to avoid. The size of a Boolean on the JVM, of course, depends on the VM, even bytes are what you need to consider, especially when people generate large Stream data. Again, this is definitely not what I usually consider and certainly represents a micro-optimization for memory usage.
The reason I think performance is one of the key points is the SI-7266 , which captures a memory leak in the stream. Note how important it is to keep track of the byte code to make sure that there are no extra values left inside the generated class.
The difference in implementation is that the initialization of the definition of tail is the implementation of a method that checks the generator:
def tailDefined: Boolean = tlGen eq null
Instead of a field in a class.