2
type 'a list_t =
    | Empty
    | Node of 'a * 'a list_t lazy_t


type 'a node_t =
    | Empty
    | Node of 'a * 'a zlist_t
and 'a zlist_t = 'a node_t lazy_t

I don't see many differences.

The only thing I can identify is that in the 2nd type, even the Empty is put to a lazy thunk, am I right? If I am right, then what is the purpose of put Empty to thunk?

Any other differences?


Edit

I am asking the differences between the two types of list: list_t and zlist_t.

Jackson Tale
  • 25,428
  • 34
  • 149
  • 271
  • The only difference is that in one case you defined two types and the other you just have just defined one type. – newacct Nov 16 '13 at 00:40

3 Answers3

3

The problem with the first one, I think, is that Node (foo, lazy Empty) cannot be wrapped in a Lazy.t to make the evaluation of foo itself lazy.

Let me explain a little bit more with two examples :

# type 'a list_t = Empty | Node of 'a * 'a list_t lazy_t;;
type 'a list_t = Empty | Node of 'a * 'a list_t lazy_t
# Node ((print_endline "hello"; 1), lazy Empty);;
hello
- : int list_t = Node (1, lazy Empty)

Here, the evaluation of the 'a element has been performed before I could build the list. In other words, the first type definition you wrote is a definition for lists whose head element has always been evaluated. Conversely, the second definition does not force you to evaluate the head of the list in order to build it.

# type 'a node_t = Empty | Node of 'a * 'a zlist_t and 'a zlist_t = 'a node_t lazy_t;;
type 'a node_t = Empty | Node of 'a * 'a zlist_t
and 'a zlist_t = 'a node_t lazy_t
# let x = ((lazy (Node ((print_endline "hello"; 1), lazy Empty))): int zlist_t);;
val x : int zlist_t = <lazy>

The evaluation of the element of type 'a contained in the head of the list takes place when you first force the list, for instance as follows:

# match Lazy.force x with Empty -> () | Node _ -> ();;
hello
- : unit = ()
Jonathan Protzenko
  • 1,709
  • 8
  • 13
  • What do you mean? could you please explain it more or with an example? – Jackson Tale Nov 15 '13 at 17:03
  • "he first type definition you wrote is a definition for lists whose head element has always been evaluated." Same with the second one. "`let x = ((lazy (Node ((print_endline "hello"; 1), lazy Empty))): int zlist_t);;`" You are explicitly wrapping the structuring in a `lazy`. The same thing with the first one: `let x = ((lazy (Node ((print_endline "hello"; 1), lazy Empty))): int list_t lazy_t);;` – newacct Nov 16 '13 at 00:38
  • @newacct I think you're missing the point. I'll comment on Martin's reply. – gasche Nov 16 '13 at 08:20
  • @gasche: The question only makes sense if he's asking about the equivalence between `list_t` and `node_t`. Obviously, since the second way defines two type names, there's one named type (the type alias `zlist_t`) that doesn't have an explicit name in the first way. That seems to be all you're saying. If having that second name is what he cared about, he can always, at any time, just define another type `type 'a zlist_t = 'a list_t lazy_t` and get the exact same portfolio of type names. It seems silly if that's what he's asking. – newacct Nov 16 '13 at 10:47
  • @newacct I believe this answer is correct as there actually is a diff. see http://blog.enfranchisedmind.com/2007/01/ocaml-lazy-lists-an-introduction/ – Jackson Tale Nov 16 '13 at 11:37
  • "The question only makes sense if he's asking about the equivalence between `list_t` and `node_t`.": no, I believe he's asking about the difference between `list_t` and `zlist_t`, which is actually interesting. `node_t` is clearly an internal name used to define the type, but not emphasize for direct use by the user; it's the type of "forced list cells", not of lazy lists. – gasche Nov 16 '13 at 12:38
  • @gasche you are right, there are obviously two types of list `list_t` and `zlist_t` and I am asking about the diff between these two list types. – Jackson Tale Nov 16 '13 at 12:59
1

There is absolutely no difference between 'a node_t and 'a list_t, since you can substitute the definition of the alias 'a zlist_t into the definition of 'a node_t and what you get is exactly analogous to 'a list_t.

You said you are asking about the difference between 'a list_t and 'a zlist_t. Well, 'a zlist_t is just a type alias for 'a node_t lazy_t. As we noted above that node_t and list_t are equivalent, that means 'a zlist_t, i.e. 'a node_t lazy_t, is equivalent to 'a list_t lazy_t. So your question is basically asking what is the difference between 'a list_t and 'a list_t lazy_t.

Well, from looking at it, the difference is simple -- one is the other wrapped in a lazy_t, which means it's the lazy version of the other. If you have a value of type 'a list_t, it means that the first cons cell is already evaluated, since it's value must either be Empty or Node, without evaluating lazy expressions. Further, if it is Node, the first item must also be evaluated, since it lives inside the Node without a lazy_t. On the other hand, if you have a value of type 'a list_t lazy_t, it means the first cons cell may not be evaluated; you don't get an 'a list_t until you force it.

newacct
  • 119,665
  • 29
  • 163
  • 224
0

There's no difference. 'a zlist_t is an alias for 'a node_t lazy_t. After performing the substitution, we end up with:

type 'a node_t =
    | Empty
    | Node of 'a * 'a node_t lazy_t

What you could do is:

type 'a node_t =
    | Empty
    | Node of ('a * 'a node_t) lazy_t

Perhaps better written as:

type 'a node =
    | Empty
    | Node of 'a zlist

and 'a zlist = ('a * 'a node) lazy_t

Note that since OCaml 3.08 or so we can force the evaluation of lazy expressions during pattern-matching like this:

match l with
| Empty -> ...
| Node (lazy (..., ...)) ->

Don't use this definition. It's better to have the list completely lazy like in your definitions. Independently you can always make the elements of the list lazy if you wish.

Martin Jambon
  • 4,629
  • 2
  • 22
  • 28
  • Yes, there is a difference. The naming is not innocent. With the first definition, users are expected to use `'a list_t` as the primary notion of what a lazy list is. With the second, they are expected to use `'a zlist_t` as their main data structure. The second is effectively strictly more lazy than the first, whose head is forced. You and @newacct pointed out that you can regain full laziness by wrapping the head-forced list into one more thunking level; that's true, but this is not the common case this type has been designed for. – gasche Nov 16 '13 at 08:23
  • @gasche: `zlist_t` is a type alias. From the language point of view, writing `'a zlist_t` is *exactly identical* to writing `'a node_t lazy_t`. You call `'a list_t lazy_t` "wrapping the head-forced list into one more thunking level", but whatever that means, that's exactly what `'a zlist_t` (i.e. `'a node_t lazy_t`) is. There is no difference, except with having defined an extra type alias, you can write it with less characters. There is no reason to believe that "users are expected" to use one and not the other. – newacct Nov 16 '13 at 10:56
  • 1
    I believe we'll have to agree to disagree on that. I think you're wrong, there is a difference, and the expected answer (which Jonathan helpfully gave) is that there is a (surprisingly subtle for most newcomers to lazyness) difference between those two ways to define lazy lists, which is actually important in practical applications. In one case the library author will expose `map : ('a -> 'b) -> 'a list_t -> 'b list_t`, in the other `map : ('a -> 'b) -> 'a zlist_t -> 'a zlist_t` (no, not the `node_t` one), and this is not the same thing. – gasche Nov 16 '13 at 12:42