From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail1-relais-roc.national.inria.fr (mail1-relais-roc.national.inria.fr [192.134.164.82]) by walapai.inria.fr (8.13.6/8.13.6) with ESMTP id q1SAAX6O004104 for ; Tue, 28 Feb 2012 11:10:33 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Ap0BANKnTE9KfVM2kGdsb2JhbABCFrNJCCIBAQEBCQkNBxQEI4F0AQEEEgITGQEbHQEDDAYFCwc0IgERAQUBDg4GNYdkC5s2CotygnGFOT+IdAEFC4xoBgIHAQIGAQoBAQ4CCgYEAwQDCAQKDQ4BAgEChViDWwSNa4dUhxiHGD2EBA X-IronPort-AV: E=Sophos;i="4.73,495,1325458800"; d="scan'208";a="146386505" Received: from mail-ee0-f54.google.com ([74.125.83.54]) by mail1-smtp-roc.national.inria.fr with ESMTP/TLS/RC4-SHA; 28 Feb 2012 11:10:03 +0100 Received: by eekd17 with SMTP id d17so1898186eek.27 for ; Tue, 28 Feb 2012 02:10:03 -0800 (PST) Received-SPF: pass (google.com: domain of camaradetux@gmail.com designates 10.213.22.129 as permitted sender) client-ip=10.213.22.129; Authentication-Results: mr.google.com; spf=pass (google.com: domain of camaradetux@gmail.com designates 10.213.22.129 as permitted sender) smtp.mail=camaradetux@gmail.com; dkim=pass header.i=camaradetux@gmail.com Received: from mr.google.com ([10.213.22.129]) by 10.213.22.129 with SMTP id n1mr5380383ebb.30.1330423803061 (num_hops = 1); Tue, 28 Feb 2012 02:10:03 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:date:message-id:subject:from:to :cc:content-type:content-transfer-encoding; bh=1511daX5342MLH29WNJk2gN0huHAcIx1M/wD4A0p3Ws=; b=ap54x0UkHrmFEcJO/ApHWPh1A+q6YVcbMLUP7TmVwTyp1a9XEWuF2UJGp3yVK5PIwq vXu2fOMS/elzizmGm2hzUWQkTrNKBBRIzsMItqRTU1UoQyDZNryKADZAMuls2nMNiVTD LWEAVECYtbSEDCrOdfsn2RoVR9Jc6uU0JTPh0= MIME-Version: 1.0 Received: by 10.213.22.129 with SMTP id n1mr4035118ebb.30.1330423802866; Tue, 28 Feb 2012 02:10:02 -0800 (PST) Received: by 10.213.3.76 with HTTP; Tue, 28 Feb 2012 02:10:02 -0800 (PST) In-Reply-To: References: Date: Tue, 28 Feb 2012 11:10:02 +0100 Message-ID: From: Adrien To: Philippe Veber Cc: Anthony Tavener , caml users Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit X-MIME-Autoconverted: from quoted-printable to 8bit by walapai.inria.fr id q1SAAX6O004104 Subject: Re: [Caml-list] Functional GUI programming: looking for good practices Hi, I've made a kind of "type diagram" for caravel in an attempt to document it and explain the approach (see the post-scriptum on the "how"). The _main_ reason was that I was often lost in some code/behaviour paths. http://notk.org/~adrien/ocaml/caravel/t13.png (NB: graphviz wouldn't let me write "Page.t" so I used commas instead of dots in the labels; similarly, I used brackets instead of parens) This graph is far from perfect and I have other documentation efforts to do. It shows two modules in caravel: Page and Navigation. Page is a browser tab: it mostly includes the webpage and the address bar but also has to track the history for instance. Navigation is a set of Page elements. As I mentionned earlier, the components are exchanging messages (directly or through callbacks on each state change) and this graph shows the polymorphic variants which are used for messages, not objects. The diagrams are not always perfectly readable but only have a few rules: the first line of blocks is the type name and after that, the fields are listed in declaration order, possibly with "sub" polymorphic variants. For instance, for Page.action_functional, the type declaration is:   type action_functional = [ | action_common | `Request of [ request | Download of string ] ] Caravel's global architecture is close to the following diagram (I'm using module packs named "UI" and "Model"): (* you _really_ want a monospace font to display this *) < USER > | < UI (object) > | < MODEL (functional/object) > ---------------------------- | | v (A) ^ (9) ^ UI.Navigation >--- Model.Navigation Navigation | ^ (8) v ------------->| | | (5) ^ (7) --------------------------<| | | v (6) ^ (4) ^ User ---> (1) UI.Page (2) >--- Model.Page Page | ^ (3) v ------------->| Steps are numbered from 1 to A (hex: it saved space on the graph). (1) The user does an action which is caught with a GTK callback in an ocaml object which holds the various UI elements for a page. (2) The callback send()'s a message to its model. The messages are of type "Model.Page.action_functional". (3) The model has an "on_action_functional" function through which all the messages go. This function is called by an FRP fold function with two arguments: the message and a Model.Page.t which is model for the page (with React, it'd give Model.Page.t React.signal). (4) The "on_action_functional" returns two values: a new value for the model and a message of type "Model.Page.action_ui". (5) The message of type action_ui is first sent to the UI object. (6) The UI object handles the messages with a "sink" function and does whatever the model told it to do (the model decides, the ui obeys). (7) The messages are also sent to the "parent" data structure (no magic and no implicit: it's a programmer-specified callback). The main reason for this is that the elements in the set are all functional and we have to tell the set that a new version of one element is available (this is done with Navigation.action_functional's `Set). (8,9,A) The same thing happens as for Page. I'm probably wrong on some points and I also lied a bit (notably, steps 7 to A happens before steps 5 and 6 because of the issue that Daniel commented on: it's been working for me but I don't know whether the approach is always valid). My current big issue is that I'm getting lost in the flow of types. For instance, when the webpage wants to download a file, it sends a message "`Request (`Download uri)" to the model (step (2) in the diagram above). Then the model returns a message "`Request (`Download (uri, referer))" (step (4)). This message is seen by Navigation (step (8)) and forwarded up again with the same process (which is basically the action of setting a flag). More generally, because the model will often add information to the messages it receives, I have different versions of the same type: `Download of string, and `Download of (string * string); `Close of id, and `Close of (id * int option), ... I'm wondering how I could have fewer types. I could remove the type with the least number of arguments: have only `Download of (string * string option) and always use None for the string option type in the UI and always Some in the model. That wouldn't work in all cases and typing would be a (tiny) bit weaker however. Something else I'd like to have is stricter types: currently the flow of types between components is `Download of x -> `Download of y -> `Download of z, but nothing in the types prevents me from writing `Download x -> `CloseEverything -> `StartNuclearWar. That's not the biggest concern today but it's something that should eventually be improved. PS: I've generated the graph with camllexer, a hand-written parser, and some logic that took advantage of caravel's organization (module packs and predefined type names because of functors). I had tried Maxence Guesdon's Oug which did a nice first work but it doesn't currently (fully?) handle polymorphic variants and it obviously wasn't specialized for caravel. -- Adrien Nader