I have been trying to reach a sane modelling in OCaml for a "story"
data structure in a CMS. The problem is that I find myself needing
a degree of expressiveness that I can't find in the language! I do
have a working, tentative solution, but it has a few ugly aspects
that I would very much like to improve. Details follow. (Sorry
for the long post; at least I hope it's not too dense and hard to
follow).
A "full" story record is defined like this:
type full_t =
{
id: int;
title: string;
intro: string;
body: string;
}
(in reality there are other fields, but I'll ommit them for the sake
of clarity). In addition, stories can also come in "blurb" and "fresh"
types, which are essentially (non-disjoint) subsets of the type above:
type blurb_t = type fresh_t =
{ {
id: int; title: string;
title: string; intro: string;
intro: string; body: string;
} }
At last, I have a function "print_metadata" that takes as parameter
either a "full" or a "blurb" story, printing its id and title:
let print_metadata s =
Printf.printf "%d: %s\n" s.id s.title
Now, I have been looking for the best way to model this situation
in OCaml. Here are some options:
a) Use record types, as shown above. However, to avoid namespace clashes,
this would entail putting each record in its own module (neat) or at
least salting each field name (ugly). Suppose that I opt for the former
option and create the modules Full, Blurb, and Fresh, each with a type t:
type story_t = [`Full of Full.t | `Blurb of Blurb.t | `Fresh of Fresh.t]
Note that I have chosen a polymorphic variant because print_metadata only
makes sense for Full.t and Blurb.t types. However, this solution means
there can't be any code sharing between the two branchings, which is just
ridiculous considering they are essentially identical: (and in the real
world, print_metadata is a much bigger function).
let print_metadata = function
| `Full s -> Printf.printf "%d: %s\n" s.Full.id s.Full.title
| `Blurb s -> Printf.printf "%d: %s\n" s.Blurb.id s.Blurb.title
b) Use only full_t and make all fields option types. However, not only is
this cumbersome to use, but is also conceptually wrong, because it does
not capture the fact that, for example, all "blurb" stories have three
*mandatory* fields.
c) Actually put the "Objective" part of OCaml to use. This is the solution
I am using at the moment. This is what it looks like:
class story (id, title, intro, body) =
object
val id: int option = id
val title: string option = title
val intro: string option = intro
val body: string option = body
method id =
match id with
| Some thing -> thing
| None -> failwith "oops"
method title =
match title with
| Some thing -> thing
| None -> failwith "oops"
method intro =
match intro with
| Some thing -> thing
| None -> failwith "oops"
method body =
match body with
| Some thing -> thing
| None -> failwith "oops"
end
class full (id, title, intro, body) =
object
inherit story (Some id, Some title, Some intro, Some body)
end
class blurb (id, title, intro) =
object
inherit story (Some id, Some title, Some intro, None)
end
class fresh (title, intro, body) =
object
inherit story (None, Some title, Some intro, Some body)
end
let print_metadata s =
Printf.printf "%d: %s\n" s#id s#title
This last solution has two big advantages: it provides a relatively
clean interface to users of the module, and allows for code reuse
without duplication. Thanks to the way the object system in OCaml
works, the print_metadata function can operate on any objects that
have the #id and #title methods. It feels almost like the duck-typing
present in languages such as Python (though different, of course).
However, I'm still not completely happy with it, mostly because the
hackery with the optional types inside the story class is ugly. Does
someone have any clever ideas on how this could be modelled/improved?
Thanks,
Dario
___________________________________________________________
Yahoo! Answers - Got a question? Someone out there knows the answer. Try it
now.
http://uk.answers.yahoo.com/
_______________________________________________
Caml-list mailing list. Subscription management:
http://yquem.inria.fr/cgi-bin/mailman/listinfo/caml-list
Archives: http://caml.inria.fr
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
Bug reports: http://caml.inria.fr/bin/caml-bugs
Best,
Daniel
module Story : sig
type 'a t constraint 'a = [< `Full | `Blurb | `Fresh ]
(* Story creators *)
val full : ... -> `Full t
val blurb : ... -> `Blurb t
val fresh : ... -> `Fresh t
val print_meta_data : [< `Full | `Blurb ] t -> unit
end = struct
type 'a t = {
id : int;
title : string;
intro : string;
data : ...
}
...
end
Why not have different object types for each of the story types? e.g.
type full = < id: int; title: string; intro: string; body: string >
type blurb = < id: int; title: string; intro: string >
type fresh = < title: string; intro: string; body: string >
print_metadata can remain as is in your object example. In order to
allow functions that operate differently depending on which story type
they're given, use a polymorphic variant like in your first example.
If you wanted print_metadata to take such a type, it could be written
as:
let print_metadata s =
let s' =
match s with
| `Full f -> (f :> < id: int; title: string >)
| `Blurb f -> (f :> < id: int; title: string >)
in
Printf.printf "%d: %s\n" s'#id s'#title
(Note the match gook is unfortunately needed to get the typing right...)
If on the other hand you wanted to ditch objects entirely, you could
do a similar thing using modules and functors. E.g.:
module Full = struct
type t = { id: int; title: string; intro: string; body: string }
let id s = s.id
let title s = s.title
let intro s = s.intro
let body s = s.body
end
(* etc. *)
module Print_metadata(S: sig type t val id: t -> string val title: t
-> string) = struct
let f s = Printf.printf "%d: %s\n" (S.id s) (S.title s)
end
Of course, since calls to print_metadata now look like
"Print_metadata(Full).f story", you're essentially forced to write out
the types of everything, which is probably what you wanted to avoid
anyway.
HTH,
Chris
Hi,
Yes, you're right. Doing away with the inheritance -- while having the
disadvantage of forcing the redeclaration of all common fields -- does
allow me to throw away the ugliness of the option types. It is in the
end the lesser of two evils...
Cheers,
Dario
___________________________________________________________
Want ideas for reducing your carbon footprint? Visit Yahoo! For Good http://uk.promotions.yahoo.com/forgood/environment.html
> Why not have different object types for each of the story types? e.g.
Yes, other people have suggested the same, and while not a perfect
solution (because of the reduplication of fields), the object subtyping
feature of OCaml (the bit that smells a lot like duck-typing...)
is powerful enough to avoid the code duplication in functions such
as print_metadata. More important, it avoids the ugly hackery with
option types.
> If on the other hand you wanted to ditch objects entirely, you could
> do a similar thing using modules and functors. E.g.:
That is actually an interesting approach. If I'm not mistaken, the
semantics for the parameter to a functor is "a module whose signature
contains at least this", which is again similar to duck-typing...
Cheers,
Dario
___________________________________________________________
Yahoo! Answers - Got a question? Someone out there knows the answer. Try it
now.
http://uk.answers.yahoo.com/
_______________________________________________
Hi,
You mean like CDuce and/or OCamlduce?
I would use something weaker, and I wouldn't use objects
either. To me, this is essentially a dynamic scenario,
so the data type should be dynamic. For example:
type field_t = [
| `Title of string
| `Intro of string
| `Body of string
| `Field of string * string
]
type story_t = field_t list
--
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net
Hi,
Interesting idea, but how would you access the fields in a convenient
manner? Note that nothing beats record access (say, story.title) or even
object method call (story#title) in convenience and readability.
(I guess the convenience aspect could be sorted out with a syntax
extension, but then we're moving into far more complex territory
than I hope is necessary...)
Cheers,
Dario
___________________________________________________________
Want ideas for reducing your carbon footprint? Visit Yahoo! For Good http://uk.promotions.yahoo.com/forgood/environment.html
_______________________________________________
I would vote for the object solution. However, both record and object have
their own pros and cons. The actual solution should depends on your requirement
and taste. Just for an example, the following data structure is a possibility:
# type +'a t = {title:string; intro:string; extra:'a} constraint 'a = < .. >;;
# let fresh (title,intro,(body:string)) =
{title=title; intro=intro; extra=object method body=body end};;
# let blurb ((id:int),title,intro) =
{title=title; intro=intro; extra=object method id=id end};;
# let print_metadata s = Printf.printf "%d: %s\n" s.extra#id s.title;;
val print_metadata : < id : int; .. > t -> unit = <fun>
Dario Teixeira <dariot...@yahoo.com> writes:
> I have been trying to reach a sane modelling in OCaml for a "story"
> data structure in a CMS. The problem is that I find myself needing
> a degree of expressiveness that I can't find in the language! I do
> have a working, tentative solution, but it has a few ugly aspects
> that I would very much like to improve. Details follow. (Sorry
> for the long post; at least I hope it's not too dense and hard to
> follow).
--
Zheng Li
http://www.pps.jussieu.fr/~li
> I have been trying to reach a sane modelling in OCaml for a "story"
> data structure in a CMS.
(...)
> Does someone have any clever ideas on how this could be
> modelled/improved?
Also take a look at:
http://tech.groups.yahoo.com/group/ocaml_beginners/message/8654
Cheers,
V.
My taste for this sort of thing is to make all this a finite map of some
sort (might be implemented as a list, but the primitive should ensure a
form of unicity, therefore an additional layer of abstract datatype).
Then you would need a type to query over :
"field_field = [ `Title | `Intro | `Body | `Field ].
Implement a function "fetch" that returns you a field_t or raises an
exception (you can do similarily a function "mem" etc).
Then you can define
let title m = let `Title s = fetch `Title m in s;;
and give it type [> `Title ] story -> string (to be sure not to raise
an exception, modulo right phantom type invariants).
That's my own methodology for this sort of types.
Arnaud Spiwack
Dario Teixeira a écrit :
>> type field_t = [
>> | `Title of string
>> | `Intro of string
>> | `Body of string
>> | `Field of string * string
>> ]
>>
>> type story_t = field_t list
>>
> Interesting idea,
If this is right for you then what you want is property lists and
these two solutions [1,2] may be of interest to you.
Daniel
[1] http://mlton.org/PropertyList
[2] http://caml.inria.fr/pub/ml-archives/caml-list/
2005/07/1b7a28921aa0a0e002df9e67ace7b60e.fr.html
See also Jacques' follow up, where he provides a syntax extension for
these "polymorphic maps" (essentially type-safe property lists):
http://caml.inria.fr/pub/ml-archives/caml-list/2005/07/314a9eb55585d32e7876ef424f5994ae.fr.html
let rec get_title ls = match ls with
| `Title s :: _ -> s
| _ ::t -> get_title t
| [] -> raise Not_found (* sucks.. *)
--
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net
_______________________________________________
Yes, my 'solution' is just a hint. Felix actually uses
something similar to the above structure to record properties
of functions, eg 'pure', 'inline', etc.
This kind of data structure is weakly typed, but that's
part of the idea. Someone probably has an actual representation
of BibTeX data, which is similar in spirit.
Perhaps this weak typing is too weak and can be beefed up,
for example if the title is mandatory, a record PLUS
a property list.
--
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net
_______________________________________________
~~ Robert.
> let rec get_title ls = match ls with
> | `Title s :: _ -> s
> | _ ::t -> get_title t
> | [] -> raise Not_found (* sucks.. *)
>
>
>
_______________________________________________
http://caml.inria.fr/pub/ml-archives/caml-list/2004/12/a0924032de03d517cb8cb8f2adde6c94.en.html
Rich.
--
Richard Jones
Red Hat
I'm not particularly encouraging the use of OCamlduce here, but FWIW, a
solution would look like:
type title = {{ { title=Latin1 ..} }}
type intro = {{ { intro=Latin1 ..} }}
type blurb_t = {{ { id=Int .. } ++ title ++ intro }}
type fresh_t = {{ { body=Latin1 .. } ++ title ++ intro }}
type full_t = {{ blurb_t & fresh_t }} (* intersection type *)
let print_metadata s =
Printf.printf "%d: %s\n" {:s.id:} {:s.title:}
If you're ok duplicating fields, the type definitions can be simplified:
type blurb_t = {{ {id=Int title=Latin1 intro=Latin1 ..} }}
type fresh_t = {{ {body=Latin1 title=Latin1 intro=Latin1 ..} }}
type full_t = {{ {id=Int body=Latin1 title=Latin1 intro=Latin1 ..} }}
-- Alain
And a big thanks to everyone -- you've really given me a lot of interesting
ideas. For the time being, I am going for the no-inheritance object solution:
it avoids the option type mess, provides a clean interface to users of the
module, and allows code reuse thanks to OCaml's structural subtyping feature
(the "duck" in this thread's title). Moreover, while obviously not as concise
as a record, the declaration of each story variant is still quite economical:
class full (id, title, intro, body) =
object
method id: int = id
method title: string = title
method intro: string = intro
method body: string = body
end
class blurb (id, title, intro) =
object
method id: int = id
method title: string = title
method intro: string = intro
end
Etc, etc. I can live with this.
Cheers,
Dario
___________________________________________________________
Want ideas for reducing your carbon footprint? Visit Yahoo! For Good http://uk.promotions.yahoo.com/forgood/environment.html
_______________________________________________
Do you know if that syntax extension is still working with ocaml/camlp4
3.10?
Thanks for your pointer, Cheers.
--
Stefano Zacchiroli -*- PhD in Computer Science ............... now what?
zack@{cs.unibo.it,debian.org,bononia.it} -%- http://www.bononia.it/zack/
(15:56:48) Zack: e la demo dema ? /\ All one has to do is hit the
(15:57:15) Bac: no, la demo scema \/ right keys at the right time
Uhm sorry, why aren't you going for the object *with* inheritance
solution? The one you're proposing here is more lightweight than the
initial one due to the lack of option types, not due to the lack of
inheritance.
Can't you just go for:
class blurb (id, title, intro) =
object
method id: int = id
method title: string = title
method intro: string = intro
end
class full (id, title, intro, body) =
object
inherit blurb (id, title, intro)
method body: string = body
end
that way you would also gain code reuse and can maybe define printing
functions on top of each other.
Cheers.
--
Stefano Zacchiroli -*- PhD in Computer Science ............... now what?
zack@{cs.unibo.it,debian.org,bononia.it} -%- http://www.bononia.it/zack/
(15:56:48) Zack: e la demo dema ? /\ All one has to do is hit the
(15:57:15) Bac: no, la demo scema \/ right keys at the right time
_______________________________________________
http://www.math.nagoya-u.ac.jp/~garrigue/code/ocaml.html
Jacques Garrigue
From: Stefano Zacchiroli <za...@bononia.it>
> On Wed, Oct 17, 2007 at 12:21:39PM -0400, Chris King wrote:
> > See also Jacques' follow up, where he provides a syntax extension for
> > these "polymorphic maps" (essentially type-safe property lists):
> > http://caml.inria.fr/pub/ml-archives/caml-list/2005/07/314a9eb55585d32e7876ef424f5994ae.fr.html
>
> Do you know if that syntax extension is still working with ocaml/camlp4
> 3.10?
_______________________________________________
> Uhm sorry, why aren't you going for the object *with* inheritance
> solution? The one you're proposing here is more lightweight than the
> initial one due to the lack of option types, not due to the lack of
> inheritance.
>
> Can't you just go for:
>
> class blurb (id, title, intro) =
> object
> method id: int = id
> method title: string = title
> method intro: string = intro
> end
>
> class full (id, title, intro, body) =
> object
> inherit blurb (id, title, intro)
> method body: string = body
> end
While that would indeed involve a little bit of less typing, it's also
conceptually wrong, because a Full story is not a derivation of a Blurb
with an extra field.
Ideally, I should be able to declare a "platonic" Story that includes
all fields that describe a story. A Full story is just the real-world
clone of the platonic ideal, while a Blurb is a Story with the constraint
that there is no "body" field declared (and so on).
Cheers,
Dario
___________________________________________________________
Want ideas for reducing your carbon footprint? Visit Yahoo! For Good http://uk.promotions.yahoo.com/forgood/environment.html
_______________________________________________
Well, remember that in ocaml inheritance is not an instance of "is a"
relationship among classes, but rather "method inclusion" (if you really
want a name for this :)). So you can imagine that blurb (or call it
otherwise) is just a set of methods denoting how a blurb-like class
should behave, and here you have back the duck-typing mentioned in the
subject, and than you "inherit" from it both in the actual blurb class
and in the full class.
Just my 0.02€,
Cheers.
--
Stefano Zacchiroli -*- PhD in Computer Science ............... now what?
zack@{cs.unibo.it,debian.org,bononia.it} -%- http://www.bononia.it/zack/
(15:56:48) Zack: e la demo dema ? /\ All one has to do is hit the
(15:57:15) Bac: no, la demo scema \/ right keys at the right time
_______________________________________________
If you s/blurb/header/g or s/blurb/summary/g, then a Full story arguably
is-a blurb with a body tacked on.
~~ Robert.
> That seems backwards from the way OO inheritance is supposed to work.
> You don't go from a more feature-rich case to a less feature-rich case
> -- it's the other way around.
Of course it is -- that is precisely why inheritance is the wrong
formalism for my problem! What I need is a "reverse inheritance"
formalism, where a fully defined data structure sits at the root,
and whose descendants are PRUNED versions of the parent.
If sound (and I let the theoreticians decide on that), such a
formalism would be an interesting solution to the type of problem
that originated this thread.
Cheers,
Dario
___________________________________________________________
Yahoo! Answers - Got a question? Someone out there knows the answer. Try it
now.
http://uk.answers.yahoo.com/
_______________________________________________
>Hi,
>
>
>
>>That seems backwards from the way OO inheritance is supposed to work.
>>You don't go from a more feature-rich case to a less feature-rich case
>>-- it's the other way around.
>>
>>
>
>Of course it is -- that is precisely why inheritance is the wrong
>formalism for my problem! What I need is a "reverse inheritance"
>formalism, where a fully defined data structure sits at the root,
>and whose descendants are PRUNED versions of the parent.
>
>
The problem with this is that it violates one of the assumptions of
typing, that if type A is (also) a type B, than anywhere you can use a
type B, you can also use a type A. This isn't an assumption limited to
object oriented languages. And this isn't true in your example- if type
A is lacking members type B has, then it's possible to write situations
where a "real" type B can be used, but not a type A- just use a field of
type B that type A doesn't have.
I think I'd recommend rethinking your approach to the problem.
Brian
Arnaud Spiwack
Saying that A is a supertype of B is the equivelent of saying B is a
subtype of A. Same relation, different direction. In OO lingo, how
they say "B is a subtype of A" is that "B inherits from (is a subclass
of) A".
In either case, while all B's are A's, not all A's are B's. What he
wants is a case where not all A's are B's, but because of the magic
mindreading feature of the language (in conjunction with the DWIM
feature of the hardware), you can treat all A's as B's, with the system
magically filling in the missing values and methods. This is what the
mind reading is needed for- to know how to fill in the missing values.
I will note that Ocaml's row-level polymorphism allows you to invent new
supertypes of a given subtype as needed (a real nice feature, IMHO).
But what he's asking for is fundamentally nonsensical.
Brian