From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.1.3 (2006-06-01) on yquem.inria.fr X-Spam-Level: * X-Spam-Status: No, score=1.1 required=5.0 tests=AWL,MISSING_HEADERS, SPF_NEUTRAL autolearn=disabled version=3.1.3 X-Original-To: caml-list@yquem.inria.fr Delivered-To: caml-list@yquem.inria.fr Received: from mail4-relais-sop.national.inria.fr (mail4-relais-sop.national.inria.fr [192.134.164.105]) by yquem.inria.fr (Postfix) with ESMTP id BA94CBC69 for ; Wed, 17 Oct 2007 16:13:19 +0200 (CEST) X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AgAAAEq1FUdIDszoa2dsb2JhbACOTgsEBBETBA X-IronPort-AV: E=Sophos;i="4.21,289,1188770400"; d="scan'208";a="18139114" Received: from qb-out-0506.google.com ([72.14.204.232]) by mail4-smtp-sop.national.inria.fr with ESMTP; 17 Oct 2007 16:13:18 +0200 Received: by qb-out-0506.google.com with SMTP id e11so2474213qbe for ; Wed, 17 Oct 2007 07:13:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=beta; h=domainkey-signature:received:received:message-id:date:from:user-agent:mime-version:cc:subject:references:in-reply-to:content-type:content-transfer-encoding:sender; bh=Aqg/MWQGFuVPzzquNNBb6VNXuzkjBIpyGwsFpcdFKZY=; b=hXpGa11KTxAhgHPwiVVZAgl45jiIi/Foj5deW+bbtaPMtWiCsO4BGiQhG4U6XMsSIIsRyLk+ospvxkeuHfCeVWGDCo2JKQW2ZaWeeCvueTNsMG5v4KPsuscP46ivBVkqY5mTwdAdLC0M0t1qWm9iO/VFQR4+xVzwvWR1Df1GBnU= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=beta; h=received:message-id:date:from:user-agent:mime-version:cc:subject:references:in-reply-to:content-type:content-transfer-encoding:sender; b=PnMXjDLCvofl1AMLEHzD9glC3q2VM6tPdRrM1ck8xI7oYtlXYL2UPSCPHXnnILm3ZRvypg0XjF6CEap56qia/Jfup4r1/gqUPVuB2J1ulHf5wYKkana47WzWvhv9SNJKipeJAgxRQfJZ/7QTvrnDoD/9RfT823VywpIyvlxCG/Y= Received: by 10.86.51.2 with SMTP id y2mr6846726fgy.1192630396925; Wed, 17 Oct 2007 07:13:16 -0700 (PDT) Received: from ?192.168.0.5? ( [87.88.165.197]) by mx.google.com with ESMTPS id i5sm14199253mue.2007.10.17.07.13.13 (version=SSLv3 cipher=RC4-MD5); Wed, 17 Oct 2007 07:13:14 -0700 (PDT) Message-ID: <47161876.7050500@lix.polytechnique.fr> Date: Wed, 17 Oct 2007 16:13:10 +0200 From: Arnaud Spiwack User-Agent: Thunderbird 2.0.0.6 (Windows/20070728) MIME-Version: 1.0 Cc: caml-list@yquem.inria.fr Subject: Re: [Caml-list] Smells like duck-typing References: <932096.75090.qm@web54602.mail.re2.yahoo.com> In-Reply-To: <932096.75090.qm@web54602.mail.re2.yahoo.com> Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 8bit Sender: Arnaud Spiwack X-Spam: no; 0.00; lix:01 ocaml:01 subtyping:01 printf:01 printf:01 val:01 ocaml:01 model:01 clashes:01 entail:01 conceptually:01 val:01 failwith:01 failwith:01 hackery:01 Hi, I think your solution is to fully use the "Objective" part of OCaml, that is, using subtyping (technically it's polymorphic rows typing) : class full_t (id, title, intro, body) = object method id : int = id method title : string = title method intro : string = intro method body : string= body end class blurb_t (id, title, intro) = object method id : int = id method title : string = title method intro : string = intro end # let print_metadata s = Printf.printf "%d: %s\n" s#id s#title val print_metadata : < id : int; title : string; .. > -> unit = # print_metadata (new full_t (1, "title", "intro", "body"));; 1: title # print_metadata (new blurb_t (1, "title", "intro"));; 1: title Dario Teixeira a écrit : > Hi, > > 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 > >