On Mon, 6 Oct 2003, Alan Post wrote:
> In article <Pine.LNX.4.44.0310061648040.21186-100000@...>, 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
|