I don’t mean "Why didn’t the authors of Batteries make LazyList.map2
lazy, but rather “What makes its code non-lazy?”
The doc comment for LazyList.map2
in Batteries says:
Raises Different_list_size if the two lists have different lengths. Not tail-recursive, lazy. In particular, the exception is raised only after the shortest list has been entirely consumed.
I am interpreting the second sentence as “Not tail-recursive and not lazy” (as opposed to “Not tail-recursive, but lazy”).
One question is about the phrase “In particular”. How does the fact that one must consume one of the lists fully to get the exception make the function non-lazy? (Or non-tail-recursive?? No.)
Looking at the source, though, I’m not clear on what is and isn’t lazy. The definition is:
let map2 f l1 l2 =
let rec aux l1 l2 =
match (next l1, next l2) with
| (Cons (h1, t1), Cons(h2, t2)) -> lazy (Cons (f h1 h2, aux t1 t2))
| (Nil, Nil) -> nil
| (Cons _, Nil) | (Nil, Cons _) -> raise (Different_list_size "LazyList.map2")
in aux l1 l2
Compare this with LazyList.map
, whose doc comment says it is a “Lazy map”:
let map f l =
let rec aux rest = match next rest with
| Cons (x, (t : 'a t)) -> Cons (f x, lazy (aux t))
| Nil -> Nil
in lazy (aux l)
They differ in that in map2
, lazy
is applied to the Cons
, while in map
, lazy
is inside the Cons
, and applies to computation of the next node. So there is something lazy about map2
, but it’s different from map
’s laziness. I don’t understand the difference, though.
It seems as if map2
is even more lazy than map
. In map
, you immediately apply f
to the head, and the rest of the list is lazily constructed. In map2
, f
isn’t even applied to the head immediately.