caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: Adrien <camaradetux@gmail.com>
To: Philippe Veber <philippe.veber@gmail.com>
Cc: Anthony Tavener <anthony.tavener@gmail.com>,
	caml users <caml-list@inria.fr>
Subject: Re: [Caml-list] Functional GUI programming: looking for good practices
Date: Tue, 28 Feb 2012 11:10:02 +0100	[thread overview]
Message-ID: <CAP5QFJkPPtwVi-Yh8FLy_PscB-vKYkO6NscfzMopbwKpQzU=Fw@mail.gmail.com> (raw)
In-Reply-To: <CAOOOohR-zvcvHazu0JjXx2ZQ+FdNoPUQEEpNAk7em+76rU1rbg@mail.gmail.com>

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


      reply	other threads:[~2012-02-28 10:10 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-02-13 11:01 Philippe Veber
2012-02-13 15:27 ` Adrien
2012-02-14 10:02   ` Philippe Veber
2012-02-14 10:21     ` Daniel Bünzli
2012-02-14 10:39       ` Philippe Veber
2012-02-14 11:52         ` Adrien
2012-02-14 13:00           ` Daniel Bünzli
2012-02-14 13:29     ` Adrien
2012-02-13 18:13 ` Raoul Duke
2012-02-14  0:36   ` Anthony Tavener
2012-02-14 10:17     ` Philippe Veber
2012-02-14 18:02       ` Anthony Tavener
2012-02-15  4:47         ` Anthony Tavener
2012-02-22 11:57           ` Philippe Veber
2012-02-28 10:10             ` Adrien [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAP5QFJkPPtwVi-Yh8FLy_PscB-vKYkO6NscfzMopbwKpQzU=Fw@mail.gmail.com' \
    --to=camaradetux@gmail.com \
    --cc=anthony.tavener@gmail.com \
    --cc=caml-list@inria.fr \
    --cc=philippe.veber@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).