From: Brian H. <bh...@sp...> - 2003-10-07 05:00:39
|
On Mon, 6 Oct 2003, Alan Post wrote: > In article <Pine.LNX.4.44.0310061648040.21186-100000@localhost.localdomain>, Brian Hurt wrote: > > > > You might want to fix your Message-ID header generation, though > admittedly with all that stuff in front of the @, perhaps the > brokenness won't cause any actual problems. > > > I think objects are the *natural* representation for enumerations. > > You have internal state shared among multiple functions (data > > hiding), multiple implementations of the same methods (inheritance), > > and a specified contract for the API (interfaces). This is OO all > > over. > > How is the ocaml object system better for these than a struct of > closures (e.g. the current implementation)? In the system I'm thinking about (that I'm cleaning up to post as a talking-point), the enumeration for a list would be: class ['a] list_enum lst = object (this) inherit ['a] enum (* gives us count *) val l = lst method next = match l with | [] -> this (* or throw an exception? *) | h :: t -> {< l = t >} method curr = match l with | [] -> None | h :: t -> Some h end let enum l = ((new list_enum l) : 'a list_enum :> 'a enum) And compare it to the version in CVS: let enum l = let rec make lr count = Enum.make ~next:(fun () -> match !lr with | [] -> raise Enum.No_more_elements | h :: t -> decr count; lr := t; h ) ~count:(fun () -> if !count < 0 then count := length !lr; !count ) ~clone:(fun () -> make (ref !lr) (ref !count) ) in make (ref l) (ref (-1)) OK, my code is 14 lines (not counting blank lines), vr.s 20 for the closures. But I'll give that a wash. The closures example needs *three* levels of functions- but that's mainly to deal with the need for clone. By making the enum applicative, I don't need clone. Also, I don't need a reference. Count is implemented in the super class, so I don't need to reimplement it. But for all that, I'll grant that objects aren't a *huge* advantage over closures at this point. But now, let's write the enumeration for reading lines from a file: class file_enum desc = object inherit [string] destructive_enum val dsc = desc method get_next = try Some (input_line dsc) with End_of_file -> None end let enum_file desc = ((new file_enum desc): file_enum :> string enum) Here I'm getting some serious behaviors from the class destructive_enum- which implements next and curr and gaurentees that get_next will be call precisely once for each enumeration object (and in order), and makes the enumeration pseudo-applicative by caching the data and a reference to the next object. Can you even clone a file descriptor? A quick browse of the manual doesn't turn up how. But this is a result of the classes being (pseudo-)applicative, not OO. > > The type of the struct is the interface (Enum.t). Closures can have > shared, private data. Inheritance is much more flexible with the > struct-of-closures approach. You actually have a point here, now that I've spelled things out. Consider the following code: let rec enum_of_list lst = let next () = match lst with | [] -> enum_of_list [] | h :: t -> enum_of_list t and curr () = match lst with | [] -> None | h :: t -> Some h in Enum.make ~next:next ~curr:curr (* count is an optional argument I'm using the default implementation for. *) let rec enum_of_file dsc = let get_curr () = try Some (input_line dsc) with End_of_file -> None and get_next () = enum_of_file dsc in Enum.make_destructive ~get_curr:get_curr ~get_next:get_next This is basically the same as my object examples above, but using closures instead. This is basically as simple, and if it'd allow me to move map into the object where it belongs, it'd be a serious win. Basically, applying a map to a map would "collapse" the maps, so you'd only ever have one level of mapping. Which would eliminate the whole exceptions vr.s options debate, options would always be faster (and safer). I'll have to play with this tomorrow. I'm too brainfried to write code right now. Brian |