From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail4-relais-sop.national.inria.fr (mail4-relais-sop.national.inria.fr [192.134.164.105]) by walapai.inria.fr (8.13.6/8.13.6) with ESMTP id q1EDU1p8024722 for ; Tue, 14 Feb 2012 14:30:01 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AhgCAMVgOk9KfVM2kGdsb2JhbABEryN3CCIBAQEBCQkNBxQEI4FyAQEBAwESAhMZARsWBwEDDAYFCw0uIQEBEQEFARwGEwgah1oJnz4Ki3GCcIUkP4ELAgULiDmBeIEWBQECAwICAgIBAwEBBAEEAQMIAwYCBgEBAQEBAwQOAwYFBwYDAwUDBAgChDcGBgMGC4MvBI1lggGFTIsQgxU9hAM X-IronPort-AV: E=Sophos;i="4.73,416,1325458800"; d="scan'208";a="131295939" Received: from mail-ee0-f54.google.com ([74.125.83.54]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 14 Feb 2012 14:29:55 +0100 Received: by eekb47 with SMTP id b47so2579592eek.27 for ; Tue, 14 Feb 2012 05:29:54 -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; bh=pxjcIVU1vkhVfLj8xoUT5g4HlODobNvq1427xOFDwRU=; b=cYGswV5jRUheuSZGXS37oU5B4zy2ycOLj7OOG2+WqKlp9McxNBMJodaPkZDHapDnNg dNH9qq76n3GRJuqrezSCo79HvpUlst2kcI1XKr5szdd2ntvj5TyJDIjBbO4QnpmR4Bzr /vrl60R9Wb6z59oT+IqQfncts0dZXBJIZT6kQ= MIME-Version: 1.0 Received: by 10.14.51.9 with SMTP id a9mr6379469eec.92.1329226192317; Tue, 14 Feb 2012 05:29:52 -0800 (PST) Received: by 10.213.6.195 with HTTP; Tue, 14 Feb 2012 05:29:52 -0800 (PST) In-Reply-To: References: Date: Tue, 14 Feb 2012 14:29:52 +0100 Message-ID: From: Adrien To: Philippe Veber Cc: caml users Content-Type: text/plain; charset=ISO-8859-1 Subject: Re: [Caml-list] Functional GUI programming: looking for good practices On 14/02/2012, Philippe Veber wrote: > 2012/2/13 Adrien >> I've created a lablgtk branch named "adrien/react" to get react signals >> out >> of gtk properties and react events out of gtk signals (they match quite >> well). Support isn't perfect but enough to test and experiment. >> >> The issue was to write the application itself: it was way too complicated >> and it involved many many react values which had to be somehow kept alive. >> It was also not very useful. The reason was that the very first thing I >> would do with all the events was React.E.select: I would create distinct >> signals only to merge them! >> >> What I've started doing instead is to have one "object" with a >> corresponding >> event: callbacks only send a message to that event and look like "send >> (`Foo >> bar)" and from then I can use match over the message in a different >> location >> in the code, typically in an FRP context. >> > I did that too, and it does seem a good practice: a central bus for > information that is relevant to the whole application. [snip] There is one clear benefit: less links. When you have n elements that can trigger y actions, you need to setup (n*y) links. If you go through a "central bus", you will only need (n+y) links. Moreover, the callback code is often going to be very simple and you will be able to touch the UI code less. >> (* Warning: this is about work in-progress; it might not always make >> sense, >> might have some weird things and I might not be able to explain everything >> properly. *) >> >> My current application is a web browser which I want to make much more >> intelligent than the browsers available today. > > Just curious: in what way? I want to do everything but make a browser that is a mere collection of tabs without any relation between them. I consider a web browser to be a tool and I want to improve my own tools. Currently, if you don't use a hundred firefox add-ons, you get: - linear history (if you use vim, try ':h undo-tree) - linear tabs instead of a tree on screen and no way or a poor way to group pages together (including in the bookmarks) - stupid popup/ad blockers because a popup opened by a popup is treated like a page opened on purpose by the user - no way to recreate your browsing session: that you moved from page A to page B to page C, went back to page B, moved to page D, back to B and then to C again; ever browsed through your history looking for one element, and finding one which you had visited right before but still couldn't get a hold on the one you wanted? There's a lot of semantic going on in browsing these days and I want take advantage of it to improve browsing, especially in these horrible days of UI simplification, which I'm having troubles using and which I believe are dumbing down everything, moving everything away from the client and only to facebook and twitter while making all the other non-stupid uses harder. Of course, there's more to it: not throwing everything in an sqlite database, hoping it'll be good for all uses of everything, including full-text search. Faster everything. Configuration settings (chrom* has almost none and firefox has a lump of completely undocumented crap). And I want OCaml scripting inside the browser. ;-) >> For this reason, I store >> web pages in a data structure which is of course purely functional. I can >> have several layers of data structures containing objects in the same way. >> >> My objects start with a default state and evolve (in a functional way) >> through a fold according to the messages they receive. Each time the state >> changes, two sets of callbacks are triggered: first, to change the UI; >> second, to update the data structure containing the object which is needed >> because of functional updates. I also use that last mechanism to propagate >> messages from the inner objects to the outter ones. >> >> One last characteristic is that I have a UI side besides the functional >> one. >> It contains a handful of things which are needed to work with GTK.I also >> use >> it to propagate messages from the outter objects to the inner ones. >> >> This is work-in-progress and some details could be improved but I think >> that >> the big picture is there. As far as I can tell, the UI and functional >> sides >> are properly separated, constraints aren't heavy and I seem to be able to >> get the usual qualities of ocaml in GUIs. >> > What module would you recommend reading to get a taste of it? I have the "lablgtk-react" project but it's not well documented (partly because it has changed a lot and documentation hasn't been updated). Actually, it has changed so much that the "lablgtk" part has disappeared, and react, which was not needed since it's using a functor for any FRP implementation, is now not working anymore (I'll be fixing it soonish). The best example is maybe caravel itself (the browser I'm doing): http://git.ocamlcore.org/cgi-bin/gitweb.cgi?p=caravel/caravel.git;a=tree;f=src/browser The model/ folder has, huh, models which are purely functionals, and the ui/ folder has the UI code. At the top of the ui/page.ml, you can see: module Page = LablgtkReact.Core (MiniFRP) (BrowserModel.Page) and a bit below: let core = new Page.core ~state_callbacks:[propagator] $foo in That's because it's the UI which is the source of the messages. The UI send message to "core" which contains an FRP signal of a data structure. This data structure then changes according to the signal and both the UI side and the parent data structure get a message about that. The UI receives a message in through "core#msg_callbacks" with self#sink (there is usually little work to do there). The parent data structure gets a message through "core#state_callbacks" with the propagator argument in the code above. In my case, this is a `Set message which is handled like all the messages which are sent to the model (i.e. through the on_action_func function, here model/Navigation.on_action_func). I need to make 2 or 3 diagrams to show the messaging that takes place. >> PS: for layout management, I found that using the ~packing option when >> creating the widgets is usually much better than #add'ing the afterwards. >> > > I was hoping to build APIs with this kind of formulation: > > let window = panel (hpack [ > picture "picture.bmp" ; > vpack [ > button "Ok" (fun () -> set_some_event true) ; > button "Cancel" (fun () -> set_some_event false) > ] > ]) > > that is avoiding a "create and configure" style. If I can't get that, I'll > probably stick with a traditional, imperative approach. What about this? let window = GWindow.window () in let vbox = GPack.vbox ~packing:window#add () in let picture = foo "picture.bmp" ~packing:vbox#pack () in let hbox = GPack.hbox ~packing:vbox#pack ()in let ok = gbutton.button ~pack:hbox#pack () in let cancel = gbutton.button ~pack:hbox#pack () in ignore (ok#connect#clicked (fun () -> set_some_event true)); ignore (cancel#connect#clicked (fun () -> set_some_event false)) It's a bit longer but not more difficult and it avoids a number of issues you have in your code: - typing issues: you can't store a button and a box in the same list: you have to use variants which make handling a bit heavier - no easy way to access widgets (no variable name) with their "full" types - very simple API - not much freedom That can work quite well depending on what you do. If your UI only ever generates messages but never has to receive any, it should be good. As far as I remember, there is support for such code in Lablgtk-extras (on the ocaml forge). It's quite nice to have something purely declarative and for which the code can be formatted to look like the UI but I don't see this anymore as the biggest issue for GUI programming and have therefore not spent much on that recently. Regards, Adrien Nader