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.0 required=5.0 tests=AWL,DNS_FROM_RFC_ABUSE, DNS_FROM_RFC_WHOIS 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 D8B69BC69 for ; Wed, 17 Oct 2007 15:35:54 +0200 (CEST) X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AgAAALerFUfOvjGsoWdsb2JhbACOTgIBAQIFBAYJCAEX X-IronPort-AV: E=Sophos;i="4.21,289,1188770400"; d="scan'208";a="18137494" Received: from web54602.mail.re2.yahoo.com ([206.190.49.172]) by mail4-smtp-sop.national.inria.fr with SMTP; 17 Oct 2007 15:35:54 +0200 Received: (qmail 77362 invoked by uid 60001); 17 Oct 2007 13:35:52 -0000 DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=s1024; d=yahoo.com; h=X-YMail-OSG:Received:Date:From:Subject:To:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-ID; b=S5W7iPHl/mwXQ84m5+fDCaqepcAjLGiuJB2Dlo4YN5ian0gaiqQ+MG2k43Y2jICRZWNWeK9Mwc4cUF+TZw157AsiGWfDjEHuGKKpTGxjqO7WpT8YZ5LVbwMmWINWRfjSEzdvjTbMxp06VkaRYx1ahtu3z+Dyb+dVDNCSe55lR4w=; X-YMail-OSG: Mu4LuSEVM1myXbjEfoJqlaeztDj4Lw21_F5pL4bMkjcSgC_.VRqy6zSoSa6jS7nT_3ZUYq4Iu3xKD6tzEfj_9hKIb80.AdYkdJL363sG60_G9IITu0wXjyCLjpVX Received: from [82.155.120.49] by web54602.mail.re2.yahoo.com via HTTP; Wed, 17 Oct 2007 14:35:52 BST Date: Wed, 17 Oct 2007 14:35:52 +0100 (BST) From: Dario Teixeira Subject: Smells like duck-typing To: caml-list@yquem.inria.fr MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 8bit Message-ID: <932096.75090.qm@web54602.mail.re2.yahoo.com> X-Spam: no; 0.00; ocaml:01 printf:01 printf:01 model:01 ocaml:01 clashes:01 entail:01 conceptually:01 val:01 val:01 failwith:01 failwith:01 hackery:01 polymorphic:01 inherit:01 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/