From mboxrd@z Thu Jan 1 00:00:00 1970 X-Sympa-To: caml-list@inria.fr 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 q1DB1Q7Z000658 for ; Mon, 13 Feb 2012 12:01:26 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AqsDAJrsOE/RVdy2mGdsb2JhbABDnzWIGocudwgiAQEBAQEICQ0HFCeBaQUdAiwBGx4DEggBB10BEQEFASIsCYUmB4IkEpk0gl0Ki3GCcIQ4P4hzAgULiVKCCw8LAR8kAQElhDeDRQSVMo4lPYQD X-IronPort-AV: E=Sophos;i="4.73,411,1325458800"; d="scan'208";a="143993192" Received: from mail-vx0-f182.google.com ([209.85.220.182]) by mail1-smtp-roc.national.inria.fr with ESMTP/TLS/RC4-SHA; 13 Feb 2012 12:01:20 +0100 Received: by vcmm1 with SMTP id m1so5775162vcm.27 for ; Mon, 13 Feb 2012 03:01:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:from:date:message-id:subject:to:content-type; bh=BXTtnl+Vfft9BPocnXEmK+RQGKizWFlVc90EWdr5sok=; b=KWRl/3XSmYQPa0hXhtRzf4D8I+5f10G2Q09p3hL69DCy33ohGyKLlXuufGug2DQFYn LFsYjfAdF5r80/z8qoNf91GyGx6S7Sv8xlQq+Kmy9+3fDiTIXKepThu49QuoyFzxkb24 Pl1V9lmgE44h5M3FaD+RW2rThGkMkF+5uT1To= Received: by 10.52.70.175 with SMTP id n15mr6784624vdu.10.1329130880189; Mon, 13 Feb 2012 03:01:20 -0800 (PST) MIME-Version: 1.0 Received: by 10.52.90.140 with HTTP; Mon, 13 Feb 2012 03:01:00 -0800 (PST) From: Philippe Veber Date: Mon, 13 Feb 2012 12:01:00 +0100 Message-ID: To: caml users Content-Type: multipart/alternative; boundary=20cf3071c934f9461f04b8d665c6 X-Validation-by: philippe.veber@gmail.com Subject: [Caml-list] Functional GUI programming: looking for good practices --20cf3071c934f9461f04b8d665c6 Content-Type: text/plain; charset=ISO-8859-1 Dear camlers, I'm looking for advanced examples of GUI programming in functional style. As I'm aware there is no definitive answer on this topic, I'll gladly read about pragmatic approaches which may fail to be fully declarative, but do work well in practice. Lately I've been trying to write a little GUI API, replacing all mutable values by React signals ( http://erratique.ch/software/react), but it turns out to be more difficult than expected, for example with layout management. In order to compute a layout for the widgets, some information has to travel bottom up the widget hierarchy, and some goes top down. While there is a well-founded order for defining all signals, it's more difficult to group them in their respective widget and still avoid mutually recursive definitions. More generally I'd interested in good (and pragmatic !) techniques for GUI programming, with some real life code. Cheers, Philippe. --20cf3071c934f9461f04b8d665c6 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Dear camlers,

I'm looking for advanced examples of GUI programmi= ng in functional style. As I'm aware there is no definitive answer on t= his topic, I'll gladly read about pragmatic approaches which may fail t= o be fully declarative, but do work well in practice. Lately I've been = trying to write a little GUI API, replacing all mutable values by React sig= nals (http://erratique.ch/so= ftware/react), but it turns out to be more difficult than expected, for= example with layout management. In order to compute a layout for the widge= ts, some information has to travel bottom up the widget hierarchy, and some= goes top down. While there is a well-founded order for defining all signal= s, it's more difficult to group them in their respective widget and sti= ll avoid mutually recursive definitions. More generally I'd interested = in good (and pragmatic !) techniques for GUI programming, with some real li= fe code.
Cheers,
=A0 Philippe.

--20cf3071c934f9461f04b8d665c6-- 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 q1DFRugm010655 for ; Mon, 13 Feb 2012 16:27:56 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Ai4BAJ8rOU9KfVM2kGdsb2JhbABDsAMIIgEBAQEJCQ0HFAQjgXIBAQEEEgIsARsdAQMMBgULDS4hAQERAQUBHAYTCBEJh2ObegqLcYJwhHc/iHMCBQuIOYJ7AQgCBAcICAMDBgECAwcDAQECAQoEAwQEBw4GAQMIAQQEhFWDRQSNZYdNixCDFT2EAw X-IronPort-AV: E=Sophos;i="4.73,412,1325458800"; d="scan'208";a="131167323" Received: from mail-ee0-f54.google.com ([74.125.83.54]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 13 Feb 2012 16:27:55 +0100 Received: by eekb47 with SMTP id b47so2169974eek.27 for ; Mon, 13 Feb 2012 07:27: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=jfdqmm8ZCPsSAUCeloIYxpQ19VvGca1b+ZJA01DxjMo=; b=LJqhquusabfgw7Swl5E4INtg+3ifjZPT7mlQCS0tGNCQy5ULvsj3eRvEBdZ5da7/Uq cab5+XKZW6wnPGC+ZiVh66V3RKRCSUU0O7ADZsHiQyz+LUrW8BuqLcLhnV0cbZrgA7SV ldhZ2FmForP2viVZXWeaVpSQzvl4//MtiQo6s= MIME-Version: 1.0 Received: by 10.213.29.76 with SMTP id p12mr2039705ebc.127.1329146874648; Mon, 13 Feb 2012 07:27:54 -0800 (PST) Received: by 10.213.6.195 with HTTP; Mon, 13 Feb 2012 07:27:54 -0800 (PST) In-Reply-To: References: Date: Mon, 13 Feb 2012 16:27:54 +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 13/02/2012, Philippe Veber wrote: > Dear camlers, > > I'm looking for advanced examples of GUI programming in functional style. > As I'm aware there is no definitive answer on this topic, I'll gladly read > about pragmatic approaches which may fail to be fully declarative, but do > work well in practice. Lately I've been trying to write a little GUI API, > replacing all mutable values by React signals ( > http://erratique.ch/software/react), but it turns out to be more difficult > than expected, for example with layout management. In order to compute a > layout for the widgets, some information has to travel bottom up the widget > hierarchy, and some goes top down. While there is a well-founded order for > defining all signals, it's more difficult to group them in their respective > widget and still avoid mutually recursive definitions. More generally I'd > interested in good (and pragmatic !) techniques for GUI programming, with > some real life code. > Cheers, > Philippe. Since FRP is quite "new" and not well understood, I'm going to try to summarize how I understan it: it makes it possible to use functional code for a task that has typically relied on mutability, with all the benefits we're used to. 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. (* 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. 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. Generally speaking, FRP is not the silver bullet for GUIs. Maybe for Haskell but definitely not for OCaml. The main reason is probably that most C libraries have a specific API which is often very imperative. OCaml provides references, mutability and objects. If you don't have a big beautiful data structure and an actual model for the state of your program, you might want to go the easy route and not use FRP but mutability everywhere. PS: for layout management, I found that using the ~packing option when creating the widgets is usually much better than #add'ing the afterwards. Hope this helps, Adrien Nader 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 q1DIDQpN015146 for ; Mon, 13 Feb 2012 19:13:26 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Ai4BAB1SOU/RVdU2kGdsb2JhbABDsAgIIgEBAQEJCQ0HFAQjgXIBAQEEEgIsARsdAQMMBgULDS4hAQERAQUBHAYTIodjnAAKi3GCcIUEP4hzAgULiDmDTAQDBAQHDgYBAwIUBREBAgGHewSISoxoixCDFT2EIw X-IronPort-AV: E=Sophos;i="4.73,412,1325458800"; d="scan'208";a="131191144" Received: from mail-yw0-f54.google.com ([209.85.213.54]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 13 Feb 2012 19:13:23 +0100 Received: by yhfs35 with SMTP id s35so3635541yhf.27 for ; Mon, 13 Feb 2012 10:13:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-type; bh=lvpGlYomoXKYIkL9qHhuhuk5d2ag91wG6cEoHDCniVk=; b=uUqDp15WlJLx9R9YXC5DhTnviH5y/8Iy3A5WdGXcVUW3ocNSsEDO6aKsWnB5wkZqgE /wy5HAwQvGMxpV3Bk3jbrjhkIphe9OT7sWaYrICHwAMKG4+CQq2p1fYmKLr8jM5NeYAw FoVZ9pOJQSUOCwqbA2TKpTUy2XqsuPkF0G9is= Received: by 10.50.170.73 with SMTP id ak9mr28836062igc.3.1329156802252; Mon, 13 Feb 2012 10:13:22 -0800 (PST) MIME-Version: 1.0 Received: by 10.42.189.2 with HTTP; Mon, 13 Feb 2012 10:13:02 -0800 (PST) In-Reply-To: References: From: Raoul Duke Date: Mon, 13 Feb 2012 10:13:02 -0800 Message-ID: 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 Mon, Feb 13, 2012 at 3:01 AM, Philippe Veber wrote: > than expected, for example with layout management. In order to compute a > layout for the widgets, some information has to travel bottom up the widget > hierarchy, and some goes top down. While there is a well-founded order for academic thoughts from others: http://lambda-the-ultimate.org/node/2913 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 q1E0arC7024036 for ; Tue, 14 Feb 2012 01:36:53 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AhUBADyrOU/RVdS2kGdsb2JhbABDnx40iBkBiDAIIgEBAQEJCQ0HFAQjgXIBAQEDARICExkBGxILAQMBCwYFCw0NISEBAREBBQEKEgYTEhCHWgmcFwqLcYJwhSc/iHMCBQuIOYMKKwUFHx4Ig32EJASCXIVujGiLEIMVPYQi X-IronPort-AV: E=Sophos;i="4.73,414,1325458800"; d="scan'208";a="131217254" Received: from mail-wi0-f182.google.com ([209.85.212.182]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 14 Feb 2012 01:36:47 +0100 Received: by wibhn14 with SMTP id hn14so6390358wib.27 for ; Mon, 13 Feb 2012 16:36:47 -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=q76s1rkBgBX+ljome8kge/VaNZJYzuSBK3BQYX94eX4=; b=Xy3hi+TpGTWb43ivf5kghW0ZK+f1Y0TGUE2G1xqLA2dodyxLINaw5TeOZt9vAMxXHo 3dSG9rZbumu4J8yzJJQwDHVtkl5+5W/HSlOlCfbPS5L96kEHFyIcplNnneeyEApmWRL9 ETI7RAI4OZRo6EW8LABs/MKauM2nj/20NIla0= MIME-Version: 1.0 Received: by 10.216.139.9 with SMTP id b9mr7030207wej.23.1329179807285; Mon, 13 Feb 2012 16:36:47 -0800 (PST) Received: by 10.223.7.69 with HTTP; Mon, 13 Feb 2012 16:36:47 -0800 (PST) In-Reply-To: References: Date: Mon, 13 Feb 2012 17:36:47 -0700 Message-ID: From: Anthony Tavener To: Raoul Duke Cc: Philippe Veber , caml users Content-Type: multipart/alternative; boundary=0016e6da7d7641802604b8e1ca97 Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --0016e6da7d7641802604b8e1ca97 Content-Type: text/plain; charset=ISO-8859-1 I've been taking a stab at this too; for a video game. The approach I'm taking is with delimited continuations (using Oleg's delimcc). This allows me to run UI code as a coroutine. The UI code then looks like any other code, where user inputs are results from a function. Really, the UI code stops, waiting for input (while the mainline coroutine runs), then returns the input value. An example: let act = user (menu_of_list [ ("Equip",`Equip); ("Spell",`Spell); ("End",`End) ] ) in match act with | Some `Spell -> try let spellList = (* build a list of casting options for this character *) in let spell = ?.(user (menu_of_list spellList)) in let target = ?.(user (select (target_filter spell) scene)) in Cast.cast id spell target with OptionNone -> () This example creates a small menu of special options with the given string labels "Equip", etc. The "user" function is a yield of the UI coroutine, which means we await resuming with the input value. Interacting with GUI elements is handled by querying the scene or OpenGL state based on SDL input events. In this case clicking on the created "Spell" menu item will return a value of Some `Spell. Later in the code, more user interactions are handled for providing specific spell options and then targets. (The "?." prefix operator is just turning None values into exceptions, to implement user abort.) I have a yield function, using Delimcc's shift and abort: let yield level fn = shift level (fun k -> fn k; abort level () ) which is the basis of the 'user' function in the UI: let user fn = yield ui_process fn So far, I'm not sure how well this works out for a complete project. I like it so far, but I have complexity growing in some "update loop" stuff, which are little closures I add to be run each frame-update for reacting to mouse-over/hover. I've tried dabbling with FRP, but it kept getting hairy too. I will probably use it to replace my kludge of "update loop" closures. Someday, someone will figure out something slick, whether it's a way to use these tools, or something new. I'm at least happy not using "signals and slots"! Good luck in the hunt for elegant UI (code)! Tony Inside the UI code, the 'process' delimits the UI coroutine On Mon, Feb 13, 2012 at 11:13 AM, Raoul Duke wrote: > On Mon, Feb 13, 2012 at 3:01 AM, Philippe Veber > wrote: > > than expected, for example with layout management. In order to compute a > > layout for the widgets, some information has to travel bottom up the > widget > > hierarchy, and some goes top down. While there is a well-founded order > for > > academic thoughts from others: > > http://lambda-the-ultimate.org/node/2913 > > -- > Caml-list mailing list. Subscription management and archives: > https://sympa-roc.inria.fr/wws/info/caml-list > Beginner's list: http://groups.yahoo.com/group/ocaml_beginners > Bug reports: http://caml.inria.fr/bin/caml-bugs > > --0016e6da7d7641802604b8e1ca97 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable I've been taking a stab at this too; for a video game. The approach I&#= 39;m taking is with delimited continuations (using Oleg's delimcc). Thi= s allows me to run UI code as a coroutine. The UI code then looks like any = other code, where user inputs are results from a function. Really, the UI c= ode stops, waiting for input (while the mainline coroutine runs), then retu= rns the input value.

An example:

let act =3D user (= menu_of_list
=A0 [ ("Equip",`Equip);
=A0 =A0 = ("Spell",`Spell);
=A0 =A0 ("End",`End) ] ) in=

match act with
| Some `Spell ->
=A0 = =A0 try
=A0 =A0 =A0 let spellList =3D (* build a list of casting = options for this character *) in
=A0 =A0 =A0 let spell =3D ?.(use= r (menu_of_list spellList)) in
=A0 =A0 =A0 let target =3D ?.(user (select (target_filter spell) scene= )) in
=A0 =A0 =A0 Cast.cast id spell target
=A0 =A0 wit= h OptionNone -> ()

This example creates a small= menu of special options with the given string labels "Equip", et= c. The "user" function is a yield of the UI coroutine, which mean= s we await resuming with the input value. Interacting with GUI elements is = handled by querying the scene or OpenGL state based on SDL input events. In= this case clicking on the created "Spell" menu item will return = a value of Some `Spell.

Later in the code, more user interactions are handled f= or providing specific spell options and then targets. (The "?." p= refix operator is just turning None values into exceptions, to implement us= er abort.)

I have a yield function, using Delimcc's shift and abort= :

let yield level fn =3D
=A0 shift = level (fun k ->
=A0 =A0 fn k;
=A0 =A0 abort level ()= )

which is the basis of the 'user' function in th= e UI:

=A0 let user fn =3D yield ui_process fn

So far, I'm not sure how well this works out for a= complete project. I like it so far, but I have complexity growing in some = "update loop" stuff, which are little closures I add to be run ea= ch frame-update for reacting to mouse-over/hover.

I've tried dabbling with FRP, but it kept getting h= airy too. I will probably use it to replace my kludge of "update loop&= quot; closures. Someday, someone will figure out something slick, whether i= t's a way to use these tools, or something new. I'm at least happy = not using "signals and slots"!

Good luck in the hunt for elegant UI (code)!
=
=A0Tony

Inside the UI code, the = 9;process' delimits the UI coroutine

--0016e6da7d7641802604b8e1ca97-- From mboxrd@z Thu Jan 1 00:00:00 1970 X-Sympa-To: caml-list@inria.fr 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 q1EA3mrr015575 for ; Tue, 14 Feb 2012 11:03:48 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AmcBACwxOk/RVdQ2kGdsb2JhbABDn1yQSAgiAQEBAQkJDQcUBCOBcgEBAQMBEgITGQEbHQEDAQsGBQsNLiEBAREBBQEcBhMZCYdaCZwmCotxgnCFKT+IcwIFC4g5gxIBAgECAwUBBAUFAQIBBAEDAw4BAgEEP4NoHzYDAQwDBQELgy8ElTKLEIMVPYQD X-IronPort-AV: E=Sophos;i="4.73,416,1325458800"; d="scan'208";a="144149793" Received: from mail-vw0-f54.google.com ([209.85.212.54]) by mail1-smtp-roc.national.inria.fr with ESMTP/TLS/RC4-SHA; 14 Feb 2012 11:03:14 +0100 Received: by vbbfa15 with SMTP id fa15so7129167vbb.27 for ; Tue, 14 Feb 2012 02:03:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-type; bh=FVgjm7DNHbH1sbV75SPgPW2VtpBpbCzRcxaDM29d408=; b=Rsz9QGv58BgYmMTQRRvrhk/h6+BIOYOzqWaLtar5WHdhVHY5LPBxkcjhkAwEAtV7An hcY8/GIPyQtnX510m3liTwhTcKg0p9Y4JGTLB4Jt5yduesE0iCnp3T7PSNWYKILyBjmN JLVUPrJ+uSpfp6yiyJlC9t1BJyj9b10/QU4nU= Received: by 10.220.238.200 with SMTP id kt8mr3783086vcb.23.1329213793196; Tue, 14 Feb 2012 02:03:13 -0800 (PST) MIME-Version: 1.0 Received: by 10.52.90.140 with HTTP; Tue, 14 Feb 2012 02:02:53 -0800 (PST) In-Reply-To: References: From: Philippe Veber Date: Tue, 14 Feb 2012 11:02:53 +0100 Message-ID: To: Adrien Cc: caml users Content-Type: multipart/alternative; boundary=14dae9cfccb8f959df04b8e9b327 X-Validation-by: philippe.veber@gmail.com Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --14dae9cfccb8f959df04b8e9b327 Content-Type: text/plain; charset=ISO-8859-1 2012/2/13 Adrien > On 13/02/2012, Philippe Veber wrote: > > Dear camlers, > > > > I'm looking for advanced examples of GUI programming in functional style. > > As I'm aware there is no definitive answer on this topic, I'll gladly > read > > about pragmatic approaches which may fail to be fully declarative, but do > > work well in practice. Lately I've been trying to write a little GUI API, > > replacing all mutable values by React signals ( > > http://erratique.ch/software/react), but it turns out to be more > difficult > > than expected, for example with layout management. In order to compute a > > layout for the widgets, some information has to travel bottom up the > widget > > hierarchy, and some goes top down. While there is a well-founded order > for > > defining all signals, it's more difficult to group them in their > respective > > widget and still avoid mutually recursive definitions. More generally I'd > > interested in good (and pragmatic !) techniques for GUI programming, with > > some real life code. > > Cheers, > > Philippe. > > Since FRP is quite "new" and not well understood, I'm going to try to > summarize how I understan it: it makes it possible to use functional code > for a task that has typically relied on mutability, with all the benefits > we're used to. > With, I think, an additional benefit: making things difficult to write when we would have done something fishy with mutable references. It's just that sometimes it's difficult to avoid fishy stufff :o). > > > 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. I have a question on this particular aspect: suppose this bus is updated through the primitive (broadcast : message -> unit), and that I have a bool event ev that (indirectly) depends on the bus. Am I allowed to define the following event: let action = React.E.map (fun b -> if b then broadcast `action ; not b) ev In other words, am I allowed to call a primitive in a lifted function? > > > (* 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? > 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? > > > Generally speaking, FRP is not the silver bullet for GUIs. Maybe for > Haskell > but definitely not for OCaml. The main reason is probably that most C > libraries have a specific API which is often very imperative. OCaml > provides > references, mutability and objects. If you don't have a big beautiful data > structure and an actual model for the state of your program, you might want > to go the easy route and not use FRP but mutability everywhere. > Actually I don't use a GUI binding, but directly drawing primitives from sdl, it looked too difficult for me to use FRP in conjunction with an existing GUI library, for the very reason you invoke. However I shall have a close look at the way you made gtk and react coexist. That looks promising. > > > 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. Thanks for your feedback ! pH. > > > Hope this helps, > Adrien Nader > --14dae9cfccb8f959df04b8e9b327 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable

2012/2/13 Adrien <<= a href=3D"mailto:camaradetux@gmail.com" target=3D"_blank">camaradetux@gmail= .com>
On 13/02/2012, Philippe Veber <philippe.veber@gmail.com> wrote:
> Dear camlers,
>
> I'm looking for advanced examples of GUI programming in functional= style.
> As I'm aware there is no definitive answer on this topic, I'll= gladly read
> about pragmatic approaches which may fail to be fully declarative, but= do
> work well in practice. Lately I've been trying to write a little G= UI API,
> replacing all mutable values by React signals (
> http:= //erratique.ch/software/react), but it turns out to be more difficult > than expected, for example with layout management. In order to compute= a
> layout for the widgets, some information has to travel bottom up the w= idget
> hierarchy, and some goes top down. While there is a well-founded order= for
> defining all signals, it's more difficult to group them in their r= espective
> widget and still avoid mutually recursive definitions. More generally = I'd
> interested in good (and pragmatic !) techniques for GUI programming, w= ith
> some real life code.
> Cheers,
> =A0 Philippe.

Since FRP is quite "new" and not well understood, I&#= 39;m going to try to
summarize how I understan it: it makes it possible to use functional code for a task that has typically relied on mutability, with all the benefits we're used to.
With, I think, an additional benefi= t: making things difficult to write when we would have done something fishy= with mutable references. It's just that sometimes it's difficult t= o avoid fishy stufff :o).
=A0


I've created a lablgtk branch named "adrien/react" to get rea= ct 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.<= br> 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 lo= cation
in the code, typically in an FRP context.
I did that t= oo, and it does seem a good practice: a central bus for information that is= relevant to the whole application. I have a question on this particular as= pect: suppose this bus is updated through the primitive (broadcast : messag= e -> unit), and that I have a bool event ev that (indirectly) depends on= the bus. Am I allowed to define the following event:

let action =3D React.E.map (fun b -> if b then broadcast `action ; n= ot b) ev

In other words, am I allowed to call a primitive in a lifte= d function?
=A0


(* 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<= br> properly. *)

My current application is a web browser which I want to make much more
intelligent than the browsers available today.
Just curio= us: in what way?
=A0
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.<= br>
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<= br> changes, two sets of callbacks are triggered: first, to change the UI;
second, to update the data structure containing the object which is needed<= br> because of functional updates. I also use that last mechanism to propagate<= br> 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 us= e
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 tha= t
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?

=A0


Generally speaking, FRP is not the silver bullet for GUIs. Maybe for Haskel= l
but definitely not for OCaml. The main reason is probably that most C
libraries have a specific API which is often very imperative. OCaml provide= s
references, mutability and objects. If you don't have a big beautiful d= ata
structure and an actual model for the state of your program, you might want=
to go the easy route and not use FRP but mutability everywhere.
Actually I don't use a GUI binding, but directly drawing prim= itives from sdl, it looked too difficult for me to use FRP in conjunction w= ith an existing GUI library, for the very reason you invoke. However I shal= l have a close look at the way you made gtk and react coexist. That looks p= romising.
=A0


PS: for layout management, I found that using the ~packing option when
creating the widgets is usually much better than #add'ing the afterward= s.

I was hoping to build APIs with this kind of fo= rmulation:

let window =3D panel (hpack [
=A0 picture "pictur= e.bmp" ;
=A0 vpack [
=A0=A0=A0 button "Ok" (fun () -> set_some_even= t true) ;
=A0=A0=A0 button "Cancel" (fun () -> set_some_eve= nt false)
=A0 ]
])

that is avoiding a "create and config= ure" style. If I can't get that, I'll probably stick with a tr= aditional, imperative approach.
=A0
Thanks for your feedback !
pH.

=A0


Hope this helps,
Adrien Nader

--14dae9cfccb8f959df04b8e9b327-- From mboxrd@z Thu Jan 1 00:00:00 1970 X-Sympa-To: caml-list@inria.fr 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 q1EAHmB3016426 for ; Tue, 14 Feb 2012 11:17:48 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AtIBAIEzOk/RVdy2mGdsb2JhbABDp28BiCUIIgEBAQEBCAkNBxQngXIBAQEDARICLAEbHQEDAQsGBQQBBjsiAREBBQEcBjWHWgmcNwqLcYJwhSk/iHMCBQuLW3IXAwKEK4NJBJUyjiU9hAM X-IronPort-AV: E=Sophos;i="4.73,416,1325458800"; d="scan'208";a="144152591" Received: from mail-vx0-f182.google.com ([209.85.220.182]) by mail1-smtp-roc.national.inria.fr with ESMTP/TLS/RC4-SHA; 14 Feb 2012 11:17:43 +0100 Received: by vcmm1 with SMTP id m1so7202702vcm.27 for ; Tue, 14 Feb 2012 02:17:42 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-type; bh=vdTtXqqUHQpvR/xkC/McB96CLx4vEiWbUR7Glg8s79E=; b=rYxUYbala+XWhscA7vgasBotuK0z7P8R6eR8oEBV1IW3lTASf4zHliHpeVD0KYtEhq ArwxfhkgMyBYvRudWrl5AcZwEdLnREcaClYYc3SoP8FHOyf7fn9ze8WTCkYAQECImopw li97trfC8G09pF1pBuaINle/YjsIgCkL2+SS0= Received: by 10.52.70.175 with SMTP id n15mr8531894vdu.10.1329214662218; Tue, 14 Feb 2012 02:17:42 -0800 (PST) MIME-Version: 1.0 Received: by 10.52.90.140 with HTTP; Tue, 14 Feb 2012 02:17:22 -0800 (PST) In-Reply-To: References: From: Philippe Veber Date: Tue, 14 Feb 2012 11:17:22 +0100 Message-ID: To: Anthony Tavener Cc: Raoul Duke , caml users Content-Type: multipart/alternative; boundary=20cf3071c934c592a304b8e9e7ef X-Validation-by: philippe.veber@gmail.com Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --20cf3071c934c592a304b8e9e7ef Content-Type: text/plain; charset=ISO-8859-1 Hi Anthony, This looks interesting, however as I'm not very familiar with delimcc (that's a shame, I admit), I fail to understand the flow of the program. Would you mind giving a snippet of the update loop you mentionned? > So far, I'm not sure how well this works out for a complete project. I > like it so far, but I have complexity growing in some "update loop" stuff, > which are little closures I add to be run each frame-update for reacting to > mouse-over/hover. > > Good luck in the hunt for elegant UI (code)! > Let's hope I'm not just Dahu hunting! (http://en.wikipedia.org/wiki/Dahu) > > Tony > > --20cf3071c934c592a304b8e9e7ef Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Hi Anthony,

This looks interesting, however as I'm not very fami= liar with delimcc (that's a shame, I admit), I fail to understand the f= low of the program. Would you mind giving a snippet of the update loop you = mentionned?
=A0
So far, I'm not sure how well this works out = for a complete project. I like it so far, but I have complexity growing in = some "update loop" stuff, which are little closures I add to be r= un each frame-update for reacting to mouse-over/hover.



Good luck in the hunt for = elegant UI (code)!

Let's hope I'm not just Dahu hunting! <= br>(http://en.wikipedia.org/w= iki/Dahu)

=A0

=A0Tony


--20cf3071c934c592a304b8e9e7ef-- 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 q1EALDke016899 for ; Tue, 14 Feb 2012 11:21:13 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AogCAEo0Ok9KN1ZKnGdsb2JhbABDhRCrLwEBAQEBCAsJCRQngXIBAQQBI1YFCwsaAhgOAgI9ChAGG4d0AwanaZIegS+KcQgEBwMECgQHCQwIAQIBAgKDfSQDFYIlM2MEmnmNHg X-IronPort-AV: E=Sophos;i="4.73,416,1325458800"; d="scan'208";a="131268132" Received: from mail6.webfaction.com (HELO smtp.webfaction.com) ([74.55.86.74]) by mail4-smtp-sop.national.inria.fr with ESMTP; 14 Feb 2012 11:21:07 +0100 Received: from heyho.local (95-234.197-178.cust.bluewin.ch [178.197.234.95]) by smtp.webfaction.com (Postfix) with ESMTP id 2736F2078D0E; Tue, 14 Feb 2012 04:21:04 -0600 (CST) Date: Tue, 14 Feb 2012 11:21:01 +0100 From: =?utf-8?Q?Daniel_B=C3=BCnzli?= To: Philippe Veber Cc: caml users Message-ID: <4EE9238E9F474D96925E5920255FD691@erratique.ch> In-Reply-To: References: X-Mailer: sparrow 1.5 (build 1043.1) MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Disposition: inline Content-Transfer-Encoding: 8bit X-MIME-Autoconverted: from quoted-printable to 8bit by walapai.inria.fr id q1EALDke016899 Subject: Re: [Caml-list] Functional GUI programming: looking for good practices Le mardi, 14 février 2012 à 11:02, Philippe Veber a écrit : > In other words, am I allowed to call a primitive in a lifted function? No. This is documented here [1]. One way to side-step the issue is to put these updates in a queue and execute them after the update cycle ended. Best, Daniel [1] http://erratique.ch/software/react/doc/React#update From mboxrd@z Thu Jan 1 00:00:00 1970 X-Sympa-To: caml-list@inria.fr 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 q1EAdQtE017808 for ; Tue, 14 Feb 2012 11:39:27 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AoIBACE5Ok/RVdQ2kGdsb2JhbABEn2GQNwgiAQEBAQkJDQcUBCOBcgEBAQMBEgITGQEbHQEDAQsGBQQHOyIBEQEFARwGEyKHWgmfNAqLcYJwhSQ/gQsCBQuMTRoCg0wfNgMVgzsElTKOJT2EAw X-IronPort-AV: E=Sophos;i="4.73,416,1325458800"; d="scan'208";a="131271196" Received: from mail-vw0-f54.google.com ([209.85.212.54]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 14 Feb 2012 11:39:22 +0100 Received: by mail-vw0-f54.google.com with SMTP id fa15so7162418vbb.27 for ; Tue, 14 Feb 2012 02:39:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-type; bh=ai4kwOLynPn6F17TzqBhpzuWdZCDdTcHIyUmJmzMbt4=; b=vY/I7QeLoBBzAGWoxtEIRt5+eanEJaF9oJ9CJ9IsIUQs2Kq40ZIPgzRh4DDPxHkUx4 qGon1VBkhLUl0R9cwlnaIZ05wtvfBjQBUjadW/SBs4TAFh21pSah4o0NlK4FrVLXrL9Q ciXeD8/N+8ubgD2EUrt1yh7aoKsMFvxqGVkj8= Received: by 10.52.22.46 with SMTP id a14mr8521787vdf.27.1329215962293; Tue, 14 Feb 2012 02:39:22 -0800 (PST) MIME-Version: 1.0 Received: by 10.52.90.140 with HTTP; Tue, 14 Feb 2012 02:39:02 -0800 (PST) In-Reply-To: <4EE9238E9F474D96925E5920255FD691@erratique.ch> References: <4EE9238E9F474D96925E5920255FD691@erratique.ch> From: Philippe Veber Date: Tue, 14 Feb 2012 11:39:02 +0100 Message-ID: To: =?ISO-8859-1?Q?Daniel_B=FCnzli?= Cc: caml users Content-Type: multipart/alternative; boundary=20cf3071c9104324b004b8ea3500 X-Validation-by: philippe.veber@gmail.com Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --20cf3071c9104324b004b8ea3500 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Hi Daniel, 2012/2/14 Daniel B=FCnzli > Le mardi, 14 f=E9vrier 2012 =E0 11:02, Philippe Veber a =E9crit : > > > In other words, am I allowed to call a primitive in a lifted function? > No. This is documented here [1]. Well I did read the paragraph, but thought the described limitation might be about updating a signal from different threads. Of course it makes sense that even in a single-threaded code, an update cycle should not be interrupted by another if there are shared dependencies. May I nevertheless humbly suggest to add the "not calling a primitive inside a lifted function" rule more explicitely, as it is a tempting sin for a beginner? > One way to side-step the issue is to put these updates in a queue and > execute them after the update cycle ended. > That seems a good work-around, to gather all side-effects in one place, namely a (unit -> unit) Queue.t where to defer all calls to the primitives. That do help, thanks ! ph. > Best, > > Daniel > > [1] http://erratique.ch/software/react/doc/React#update > --20cf3071c9104324b004b8ea3500 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Hi Daniel,

2012/2/14 Daniel B=FCnzli <daniel.bu= enzli@erratique.ch>
Le mardi, 14 f=E9vrier 2012 =E0 11:02, Philippe Veber a =E9crit :

> In other words, am I allowed to call a primitive in a lifted function?=
No. This is documented here [1].
Well I did read th= e paragraph, but thought the described limitation might be about updating a= signal from different threads. Of course it makes sense that even in a sin= gle-threaded code, an update cycle should not be interrupted by another if = there are shared dependencies. May I nevertheless humbly suggest to add the= "not calling a primitive inside a lifted function" rule more exp= licitely, as it is a tempting sin for a beginner?
=A0
One way to side-= step the issue is to put these updates in a queue and execute them after th= e update cycle ended.
=A0That seems a good work-around, to gather all side-effe= cts in one place, namely a (unit -> unit) Queue.t where to defer all cal= ls to the primitives.
That do help, thanks !
ph.


Best,

Daniel

[1] http://erratique.ch/software/react/doc/React#update

--20cf3071c9104324b004b8ea3500-- 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 q1EBq8sa020668 for ; Tue, 14 Feb 2012 12:52:08 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AtQCAKlJOk9KfVM2kGdsb2JhbABDrx91CCIBAQEBCQkNBxQEI4FyAQEBAwESAhMZARsdAQMMBgULDS4hAQERAQUBHAYTIodanEAKi3GCcIUmP4hzAgULiDmBeIEWBQEFAgIEAwMQBQgBAwcBAgMHCwMGBQcGAwMFBwgCEod0BI1lh02LEIMVPYQD X-IronPort-AV: E=Sophos;i="4.73,416,1325458800"; d="scan'208";a="131282014" 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 12:52:03 +0100 Received: by eekb47 with SMTP id b47so2544406eek.27 for ; Tue, 14 Feb 2012 03:52: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=BhN8FCdAvgoIykKmkEgyFRvbLzLQ1OSYAI0WvyzoZjc=; b=T/y8kZFP0l/+4BjEvqqjnR42fIX5knKbrg9dqyOUz3wKHmdV1xxmfxuDjW30In2lAb GUNvU4vzRFZi8IX8dmgusG+jusp4a8SrivTj14HAqCQnbB/3KHHwWQmLqRZGVGvERcxI EzuWH+8HOQOSukJ45JFl1YAa2Ky+uE1uL1RH8= MIME-Version: 1.0 Received: by 10.213.8.133 with SMTP id h5mr387361ebh.13.1329220322992; Tue, 14 Feb 2012 03:52:02 -0800 (PST) Received: by 10.213.6.195 with HTTP; Tue, 14 Feb 2012 03:52:02 -0800 (PST) In-Reply-To: References: <4EE9238E9F474D96925E5920255FD691@erratique.ch> Date: Tue, 14 Feb 2012 12:52:02 +0100 Message-ID: From: Adrien To: Philippe Veber Cc: =?ISO-8859-1?Q?Daniel_B=FCnzli?= , 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 q1EBq8sa020668 Subject: Re: [Caml-list] Functional GUI programming: looking for good practices On 14/02/2012, Philippe Veber wrote: > Hi Daniel, > > 2012/2/14 Daniel Bünzli > >> Le mardi, 14 février 2012 à 11:02, Philippe Veber a écrit : >> >> > In other words, am I allowed to call a primitive in a lifted function? >> No. This is documented here [1]. > > Well I did read the paragraph, but thought the described limitation might > be about updating a signal from different threads. Of course it makes sense > that even in a single-threaded code, an update cycle should not be > interrupted by another if there are shared dependencies. May I nevertheless > humbly suggest to add the "not calling a primitive inside a lifted > function" rule more explicitely, as it is a tempting sin for a beginner? Quite often, doing so does not make a lot of sense. Suppose that you have a signal A. An external event triggers an update which will create A1. But during that update, you trigger another update which will create A2. Now, how does your signal change? Is A2 going to be the next value of A or is will it be A1? A simple (and probably not proper) implementation of FRP that I've done simply used A2 and then A1: the additional work that had been done for A2 was being dropped. Some implementations or approaches will probably solve that one way or another but that's not my point: when you do that, the code becomes non-obvious. >> One way to side-step the issue is to put these updates in a queue and >> execute them after the update cycle ended. >> > That seems a good work-around, to gather all side-effects in one place, > namely a (unit -> unit) Queue.t where to defer all calls to the primitives. > That do help, thanks ! > ph. I've had to use that until I changed the order of some operations in my code. I might have managed to break a bigger cycle into two smaller and independant cycles but I need to check, document, triple-check, ... what I've done. Anyway, I quickly benchmarked "GLib.Timeout.Add ~ms cb" from gtk/lablgtk2 which calls cb after at least ms milliseconds from the mainloop. Its latency was around 122µs for ms = 0 in my case (obviously, the program was otherwise idle). I think you could also use lwt here but I haven't done anything with it. Btw, I think it's worthwhile to mention some issues that can create such a need. In my case of a web browser, web pages have javascript and the javascript can create a new page and get a corresponding object. var popup = window.open (); // or something like that In webkit-gtk (and probably all other webkit ports), this triggers a GTK signal to which you can attach a callback which returns a WebView object. However, my code does not allow a webpage to create a new page: all it can do is request that one gets created by the Zipper which holds the pages. So the page changes its state and stores a request for a pop-up from the callback. The request is seen by the Zipper which creates a new page in the UI side of my code. Once the page is ready, it gets added to the Zipper and a message is sent to the parent page. All that has been done in the callback. It now works with my "MiniFRP" implementation but it definitely had the A/A1/A2 issue I mentionned above. If you put the updates in a queue and delay them, you're probably not going to solve this issue which turns out to be pretty simple. With the callback, you've started an update cycle and you cannot finish that update cycle before a new page, and therefore, another update cycle, has taken place. The API of webkit-gtk could allow to get an ID for the popup request, exit the callback with only the promise the answer will be given at some point, and do the actual work outside of the callback. But it doesn't. And there will always be APIs like that and we need to find how to handle this. Regards, Adrien Nader 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 q1ED1314023762 for ; Tue, 14 Feb 2012 14:01:05 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AogCAAJaOk9KN1ZKnGdsb2JhbABEhRSrMAEBAQEBCAsJCRQngXIBAQQBI1YFCwsaAhgOAgI9ChAGG4d0Awaqa4owgS+KKgcCEC4IBAcDBAoEBwUEDAsDAoN9JAIBFQOCIjNjBJp5jR4 X-IronPort-AV: E=Sophos;i="4.73,416,1325458800"; d="scan'208";a="131290779" Received: from mail6.webfaction.com (HELO smtp.webfaction.com) ([74.55.86.74]) by mail4-smtp-sop.national.inria.fr with ESMTP; 14 Feb 2012 14:01:04 +0100 Received: from heyho.local (95-234.197-178.cust.bluewin.ch [178.197.234.95]) by smtp.webfaction.com (Postfix) with ESMTP id DEC76209EE24; Tue, 14 Feb 2012 07:01:01 -0600 (CST) Date: Tue, 14 Feb 2012 14:00:58 +0100 From: =?utf-8?Q?Daniel_B=C3=BCnzli?= To: Adrien Cc: Philippe Veber , caml users Message-ID: In-Reply-To: References: <4EE9238E9F474D96925E5920255FD691@erratique.ch> X-Mailer: sparrow 1.5 (build 1043.1) MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Disposition: inline Content-Transfer-Encoding: 8bit X-MIME-Autoconverted: from quoted-printable to 8bit by walapai.inria.fr id q1ED1314023762 Subject: Re: [Caml-list] Functional GUI programming: looking for good practices Le mardi, 14 février 2012 à 12:52, Adrien a écrit : > Quite often, doing so does not make a lot of sense. > Suppose that you have a signal A. An external event triggers an update which > will create A1. But during that update, you trigger another update which > will create A2. Now, how does your signal change? Is A2 going to be the next > value of A or is will it be A1? Yes it doesn't make any sense at all. An update cycle is made under a synchrony hypothesis, everything therein happens simultaneously, at the same time t. The semantics of a signal (resp. event) [1] is a *function* from time to a value (resp. optional value). There's no way a signal (resp. event) can have two different values for the same time t, this should raise a red flag. Besides by definition two primitive events cannot (should not, since the system cannot enforce it) occur at the same time t [2]. When in doubt, it helps to get back to the semantics to see if your interfacing mechanic to react actually respects FRP's semantics. And if it doesn't and you can't fix it, I wouldn't try to use react for the problem. Best, Daniel P.S. to Philipe I'm not exactly sure what you problem is. But if you are trying to define a signal that somehow depends on itself, make sure you did understand the {E,S}.fix combinators [3]. I'm sure I had to use it once or twice to define the (very primitive) UI of the breakout.ml example from the distribution. [1] http://erratique.ch/software/react/doc/React#sem [2] http://erratique.ch/software/react/doc/React#simultaneity [3] http://erratique.ch/software/react/doc/React#recursion 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 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 q1EI2CKx005577 for ; Tue, 14 Feb 2012 19:02:16 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AngBAHygOk9KfVI0imdsb2JhbABDp3cBiCcIIgEBAQoJDQcSBiOBcgEBAQMBEgITGQEbHQEDAQsGBQs7IQEBEQEFARwGEyKHWgmfTwqLcYJwhQ4/gQsCBQuIOYMTAwQICQEJBw8FhQuDSQSISoxoixCDFT2EIg X-IronPort-AV: E=Sophos;i="4.73,417,1325458800"; d="scan'208";a="131340950" Received: from mail-ww0-f52.google.com ([74.125.82.52]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 14 Feb 2012 19:02:15 +0100 Received: by wgbds10 with SMTP id ds10so232421wgb.9 for ; Tue, 14 Feb 2012 10:02:15 -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=7/imjnnOzeN6etC/J/nclQt00UydGO5KXiEmvvOh/KI=; b=pvreMbDMq7g9tta2vnrB9dTCsC1MvBXdrNNQP5A+2zpVqNJs6CK9BFQFQdsw9vqP6r xV+pY84+btFrjWSHedlEOyO7tcwajRBQ1xlYmrhBf9z8eYL4E/GrOgGPtJH81xI3CU8e D9xHw11VKWvA0w1C1krUYtbGg7XsDWwhWZC+4= MIME-Version: 1.0 Received: by 10.216.139.197 with SMTP id c47mr1343684wej.15.1329242535345; Tue, 14 Feb 2012 10:02:15 -0800 (PST) Received: by 10.223.7.69 with HTTP; Tue, 14 Feb 2012 10:02:15 -0800 (PST) In-Reply-To: References: Date: Tue, 14 Feb 2012 11:02:15 -0700 Message-ID: From: Anthony Tavener To: Philippe Veber Cc: caml users Content-Type: multipart/alternative; boundary=0016e6de00b123bc8c04b8f0657b Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --0016e6de00b123bc8c04b8f0657b Content-Type: text/plain; charset=ISO-8859-1 Apologies Philippe, this is a bit long... The "update loop" I mentioned might be a bit of a red-herring, as I'm only using that for continuously active UI elements: such as changing cursor to represent the action which would be taken on click. It has nothing to do with the basic UI flow. I didn't understand delimcc right away, and I hardly understand it now! :) I was looking to write UI code much as your example of packing buttons together with directly bound activation functions. Here's my "menu_of_list", which takes a list of string * 'a pairs, calling GUI code to make a menu using the strings as labels, and using the 'a values as return values... let menu_of_list lst return = (* snipped: preamble which handles positioning, stacking order, getting font, etc *) Gui.add_widget gui (new Gui.vbox pos stacking spacing (lst |> List.map (fun (label,value) -> Gui.wit_fn gui (new Gui.rectangle_text w h fnt label) (fun () -> return (Some value) ) ))) The important part here is the "return" function. This will resume the UI coroutine. It is given to "menu_of_list" by the UI code, then when the GUI has an activation of a menu button this "return" is called... resuming the UI where it left off, and with the "Some value" which was associated to the button. The magic of how this works is in delimcc capturing portions of the run stack. Here I've extracted the relevant bits of code: (* this runs as a coroutine with the "mainline" *) let ui ui_process () = (* in actual code, this menu comes after a "right click", for special actions *) let act = yield ui_process (menu_of_list coord [ ("Equip",`Equip); ("Spell",`Spell); ("End",`End) ] ) in (* ... handle whatever action the user chose! *) (* given SDL events, calls activation functions of 'hit' widgets *) let gui_react gui events = let hits = gui_select gui events in match hits with | h::t -> begin match get_binding gui h with | Some f -> f () (* runs activation function of widget, such as "return" to coroutine! *) | None -> () end | [] -> () let _ = (* A prompt is delimcc's mechanism for marking the stack to -limit- the * continuation, rather than creating whole-program continuations. *) (* So here, the "ui" is initially run, and will *resume* this mainline continuation * at the end of the "user" function call. *) let ui_prompt = new_prompt () in push_prompt ui_prompt (ui ui_prompt); ... (* in mainloop *) gui_react gui events; ---------------------- Now I'm going to restate the yield function here, and try to explain... let yield level fn = shift level (fun k -> fn k; abort level () ) "shift" and "abort" are delimcc. This runs the given function "fn" with a continuation 'k'... so this continuation is the "return" function passed to menu_of_list, and therefore bound to each menu-button. The abort exits the "ui coroutine", resuming the mainline, hence the name: yield. The continuation 'k' comes from the shift... the continuation is the code "outside" of the shift call... so when it's called (by the mainline's gui_react), it resume at the end of 'yield' and keep on going... in this example, handling the returned action! I hope this conveys at least the gist of what's going on... I read a lot of papers over-and-over, not understanding... although none were specifically GUI. Delimited continuations have numerous applications and a surprising number of configurations for just a pair of functions! (Abort is really a special case of "reset"... so it's shift and reset, in ump-teen configurations.) I'll try to explain if you have any further questions! However, I'm still trying to sort out how best to write my GUI code -- there is a lot of room for improvement. :) -Tony PS. I'd like to blame X. Leroy ;)... for a post some 10 years ago replying to someone's attempt to do a GUI in OCaml... Xavier casually replied something like "oh, you can use coroutines". That was a wild goose chase (or Dahu?)! Delimcc didn't exist at the time, and it's native-code implementation came about just a few years ago (thank-you Oleg!). Even then, I had no idea how I was supposed to use a coroutine to write GUI code! Oh well, it was an adventure, and I still don't know if this is a good thing, but I like it a lot more than "signals and slots" -- the usual scattered GUI code which is connected by messages/events. On Tue, Feb 14, 2012 at 3:17 AM, Philippe Veber wrote: > Hi Anthony, > > This looks interesting, however as I'm not very familiar with delimcc > (that's a shame, I admit), I fail to understand the flow of the program. > Would you mind giving a snippet of the update loop you mentionned? > > >> So far, I'm not sure how well this works out for a complete project. I >> like it so far, but I have complexity growing in some "update loop" stuff, >> which are little closures I add to be run each frame-update for reacting to >> mouse-over/hover. >> >> > > Good luck in the hunt for elegant UI (code)! >> > > Let's hope I'm not just Dahu hunting! > (http://en.wikipedia.org/wiki/Dahu) > > > >> >> Tony >> >> > --0016e6de00b123bc8c04b8f0657b Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Apologies Philippe, this is a bit long...

The "updat= e loop" I mentioned might be a bit of a red-herring, as I'm only u= sing that for continuously active UI elements: such as changing cursor to r= epresent the action which would be taken on click. It has nothing to do wit= h the basic UI flow.

I didn't understand delimcc right away, and I hardl= y understand it now! :)

I was looking to write UI = code much as your example of packing buttons together with directly bound a= ctivation functions.

Here's my "menu_of_list", which takes a l= ist of string * 'a pairs, calling GUI code to make a menu using the str= ings as labels, and using the 'a values as return values...

let menu_of_list lst return =3D
=A0 (* snipped: pr= eamble which handles positioning, stacking order, getting font, etc *)
=A0 Gui.add_widget gui
=A0 =A0 (new Gui.vbox pos stacking s= pacing
=A0 =A0 =A0 (lst |> List.map
=A0 =A0 =A0 =A0 (fun (label,= value) ->
=A0 =A0 =A0 =A0 =A0 Gui.wit_fn gui
=A0 =A0= =A0 =A0 =A0 =A0 (new Gui.rectangle_text w h fnt label)
=A0 =A0 = =A0 =A0 =A0 =A0 (fun () -> return (Some value) ) )))

The important part here is the "return" funct= ion. This will resume the UI coroutine. It is given to "menu_of_list&q= uot; by the UI code, then when the GUI has an activation of a menu button t= his "return" is called... resuming the UI where it left off, and = with the "Some value" which was associated to the button.

The magic of how this works is in delimcc capturing por= tions of the run stack. Here I've extracted the relevant bits of code:<= /div>


(* this runs as a coroutine with th= e "mainline" *)
let ui ui_process () =3D

=A0 (* in actual cod= e, this menu comes after a "right click", for special actions *)<= /div>
=A0 let act =3D yield ui_process (menu_of_list coord
= =A0 =A0 [ ("Equip",`Equip);
=A0 =A0 =A0 ("Spell",`Spell);
=A0 =A0 =A0 ("E= nd",`End) ] ) in
=A0 (* ... handle whatever action the user = chose! *)


(* given SDL events, call= s activation functions of 'hit' widgets *)
let gui_react gui events =3D
=A0 let hits =3D gui_select gui= events in
=A0 match hits with
=A0 | h::t -> begin
=A0 =A0 match get_binding gui h with
=A0 =A0 | Some f -&= gt; f () =A0(* runs activation function of widget, such as "return&quo= t; to coroutine! *)
=A0 =A0 | None -> ()
=A0 =A0 end
=A0 | [] ->= ()

let _ =3D
=A0 (* A prompt is delimcc= 's mechanism for marking the stack to -limit- the
=A0 =A0* co= ntinuation, rather than creating whole-program continuations. *)
=A0 (* So here, the "ui" is initially run, and will *resume*= this mainline continuation
=A0 =A0* at the end of the "user= " function call. *)
=A0 let ui_prompt =3D new_prompt () in
=A0 push_prompt ui_prompt (ui ui_prompt);

=A0 ...<= /div>

=A0 (* in mainloop *)
=A0 =A0 gui_react = gui events;
=A0=A0
----------------------
Now I'm going to restate the yield function here, and try to explain...=

let yield level fn =3D
=A0 shift level = (fun k ->
=A0 =A0 fn k;
=A0 =A0 abort level () )

"shift" and "abort" are delimcc. This runs t= he given function "fn" with a continuation 'k'... so this= continuation is the "return" function passed to menu_of_list, an= d therefore bound to each menu-button. The abort exits the "ui corouti= ne", resuming the mainline, hence the name: yield.

The continuation 'k' comes from the shift... th= e continuation is the code "outside" of the shift call... so when= it's called (by the mainline's gui_react), it resume at the end of= 'yield' and keep on going... in this example, handling the returne= d action!

I hope this conveys at least the gist of what's goi= ng on... I read a lot of papers over-and-over, not understanding... althoug= h none were specifically GUI. Delimited continuations have numerous applica= tions and a surprising number of configurations for just a pair of function= s! (Abort is really a special case of "reset"... so it's shif= t and reset, in ump-teen configurations.)

I'll try to explain if you have any further questio= ns! However, I'm still trying to sort out how best to write my GUI code= -- there is a lot of room for improvement. :)

=A0-Tony

PS. I'd like to blame X. Leroy ;)... = for a post some 10 years ago replying to someone's attempt to do a GUI = in OCaml... Xavier casually replied something like "oh, you can use co= routines". That was a wild goose chase (or Dahu?)! Delimcc didn't = exist at the time, and it's native-code implementation came about just = a few years ago (thank-you Oleg!). Even then, I had no idea how I was suppo= sed to use a coroutine to write GUI code! Oh well, it was an adventure, and= I still don't know if this is a good thing, but I like it a lot more t= han "signals and slots" -- the usual scattered GUI code which is = connected by messages/events.


On Tue, Feb 14, 20= 12 at 3:17 AM, Philippe Veber <philippe.veber@gmail.com> wrote:
Hi Anthony,

This looks interesting, however as I'm not very fami= liar with delimcc (that's a shame, I admit), I fail to understand the f= low of the program. Would you mind giving a snippet of the update loop you = mentionned?
=A0
So far, I'm not sure how we= ll this works out for a complete project. I like it so far, but I have comp= lexity growing in some "update loop" stuff, which are little clos= ures I add to be run each frame-update for reacting to mouse-over/hover.



Go= od luck in the hunt for elegant UI (code)!

Let's hope I'm not just Dahu hunt= ing!
(h= ttp://en.wikipedia.org/wiki/Dahu)

=A0

=A0Tony



--0016e6de00b123bc8c04b8f0657b-- 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 q1F4lHQP027474 for ; Wed, 15 Feb 2012 05:47:17 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AjkBAPo2O09KfVI0imdsb2JhbABDqAYBiE8IIgEBAQoJDQcSBiOBcgEBAQMBEgITGQEbHQEDAQsGBQs7IQEBEQEFARwGEyKHWgmcJgqLcYJwhHo/iHMCBQuIOYMaAwgCBwQBCQcBBAENBQECBQODeUsCG4MvBIhLjGmLEoMVPYQi X-IronPort-AV: E=Sophos;i="4.73,421,1325458800"; d="scan'208";a="131389906" Received: from mail-ww0-f52.google.com ([74.125.82.52]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 15 Feb 2012 05:47:11 +0100 Received: by wgbds10 with SMTP id ds10so672930wgb.9 for ; Tue, 14 Feb 2012 20:47:11 -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=BoQVQekap0WO3T7oGNJA6Mu5l4bmmOTNGYCqDSbMLpg=; b=mycOCsHMF2d2OInf69RN55VBeWsTmPz2HwR6g3RHX99jelvE+4avDD1abvLo0M96Gf tibeyPBYrhGc2MyiGbLEwNG7j1xuAmT0sfsq/1R19uu9CiD6WLMlDaIea3eITkaNUEZY ytvkfHwlV5kutFwAXT5LPOn3gx0xNpvM4/9SA= MIME-Version: 1.0 Received: by 10.216.138.86 with SMTP id z64mr2050276wei.31.1329281230912; Tue, 14 Feb 2012 20:47:10 -0800 (PST) Received: by 10.223.7.69 with HTTP; Tue, 14 Feb 2012 20:47:10 -0800 (PST) In-Reply-To: References: Date: Tue, 14 Feb 2012 21:47:10 -0700 Message-ID: From: Anthony Tavener To: Philippe Veber Cc: caml users Content-Type: multipart/alternative; boundary=0016e6d64762933ceb04b8f9678f Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --0016e6d64762933ceb04b8f9678f Content-Type: text/plain; charset=ISO-8859-1 Hrm... when I re-read my prior post before sending, it made sense. Several hours later it seems inadequate, and I've thought of something to say more clearly... The execution of code bounces between the UI and the mainline. When the mainline processes a "gui hit" it will resume the UI code *right where it left off (yielded)*... then the UI will do something and get to another point where it yields, awaiting input and thereby resuming the mainline where it was (back to processing gui hits). Why would I want this? So that I don't have stateful UI code which has to trickle down to the right place all the time. The UI code is clearer because it doesn't need to be re-entered from the top each frame. This suits the declarative style of GUI specification, like the example you gave, since we don't need special state or messages to communicate what the GUI is doing. GUI state becomes a property of the... well, the stack. It's the current scope of the code. Hope this helps! -Tony On Tue, Feb 14, 2012 at 11:02 AM, Anthony Tavener wrote: > Apologies Philippe, this is a bit long... > > The "update loop" I mentioned might be a bit of a red-herring, as I'm only > using that for continuously active UI elements: such as changing cursor to > represent the action which would be taken on click. It has nothing to do > with the basic UI flow. > > I didn't understand delimcc right away, and I hardly understand it now! :) > > I was looking to write UI code much as your example of packing buttons > together with directly bound activation functions. > > Here's my "menu_of_list", which takes a list of string * 'a pairs, calling > GUI code to make a menu using the strings as labels, and using the 'a > values as return values... > > let menu_of_list lst return = > (* snipped: preamble which handles positioning, stacking order, getting > font, etc *) > Gui.add_widget gui > (new Gui.vbox pos stacking spacing > (lst |> List.map > (fun (label,value) -> > Gui.wit_fn gui > (new Gui.rectangle_text w h fnt label) > (fun () -> return (Some value) ) ))) > > The important part here is the "return" function. This will resume the UI > coroutine. It is given to "menu_of_list" by the UI code, then when the GUI > has an activation of a menu button this "return" is called... resuming the > UI where it left off, and with the "Some value" which was associated to the > button. > > The magic of how this works is in delimcc capturing portions of the run > stack. Here I've extracted the relevant bits of code: > > > (* this runs as a coroutine with the "mainline" *) > let ui ui_process () = > > (* in actual code, this menu comes after a "right click", for special > actions *) > let act = yield ui_process (menu_of_list coord > [ ("Equip",`Equip); > ("Spell",`Spell); > ("End",`End) ] ) in > (* ... handle whatever action the user chose! *) > > > (* given SDL events, calls activation functions of 'hit' widgets *) > let gui_react gui events = > let hits = gui_select gui events in > match hits with > | h::t -> begin > match get_binding gui h with > | Some f -> f () (* runs activation function of widget, such as > "return" to coroutine! *) > | None -> () > end > | [] -> () > > let _ = > (* A prompt is delimcc's mechanism for marking the stack to -limit- the > * continuation, rather than creating whole-program continuations. *) > (* So here, the "ui" is initially run, and will *resume* this mainline > continuation > * at the end of the "user" function call. *) > let ui_prompt = new_prompt () in > push_prompt ui_prompt (ui ui_prompt); > > ... > > (* in mainloop *) > gui_react gui events; > > ---------------------- > > Now I'm going to restate the yield function here, and try to explain... > > let yield level fn = > shift level (fun k -> > fn k; > abort level () ) > > "shift" and "abort" are delimcc. This runs the given function "fn" with a > continuation 'k'... so this continuation is the "return" function passed to > menu_of_list, and therefore bound to each menu-button. The abort exits the > "ui coroutine", resuming the mainline, hence the name: yield. > > The continuation 'k' comes from the shift... the continuation is the code > "outside" of the shift call... so when it's called (by the mainline's > gui_react), it resume at the end of 'yield' and keep on going... in this > example, handling the returned action! > > I hope this conveys at least the gist of what's going on... I read a lot > of papers over-and-over, not understanding... although none were > specifically GUI. Delimited continuations have numerous applications and a > surprising number of configurations for just a pair of functions! (Abort is > really a special case of "reset"... so it's shift and reset, in ump-teen > configurations.) > > I'll try to explain if you have any further questions! However, I'm still > trying to sort out how best to write my GUI code -- there is a lot of room > for improvement. :) > > -Tony > > PS. I'd like to blame X. Leroy ;)... for a post some 10 years ago replying > to someone's attempt to do a GUI in OCaml... Xavier casually replied > something like "oh, you can use coroutines". That was a wild goose chase > (or Dahu?)! Delimcc didn't exist at the time, and it's native-code > implementation came about just a few years ago (thank-you Oleg!). Even > then, I had no idea how I was supposed to use a coroutine to write GUI > code! Oh well, it was an adventure, and I still don't know if this is a > good thing, but I like it a lot more than "signals and slots" -- the usual > scattered GUI code which is connected by messages/events. > > > On Tue, Feb 14, 2012 at 3:17 AM, Philippe Veber wrote: > >> Hi Anthony, >> >> This looks interesting, however as I'm not very familiar with delimcc >> (that's a shame, I admit), I fail to understand the flow of the program. >> Would you mind giving a snippet of the update loop you mentionned? >> >> >>> So far, I'm not sure how well this works out for a complete project. I >>> like it so far, but I have complexity growing in some "update loop" stuff, >>> which are little closures I add to be run each frame-update for reacting to >>> mouse-over/hover. >>> >>> >> >> Good luck in the hunt for elegant UI (code)! >>> >> >> Let's hope I'm not just Dahu hunting! >> (http://en.wikipedia.org/wiki/Dahu) >> >> >> >>> >>> Tony >>> >>> >> > --0016e6d64762933ceb04b8f9678f Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Hrm... when I re-read my prior post before sending, it made sense. Several = hours later it seems inadequate, and I've thought of something to say m= ore clearly...

The execution of code bounces between the= UI and the mainline. When the mainline processes a "gui hit" it = will resume the UI code *right where it left off (yielded)*... then the UI = will do something and get to another point where it yields, awaiting input = and thereby resuming the mainline where it was (back to processing gui hits= ).

Why would I want this? So that I don't have statefu= l UI code which has to trickle down to the right place all the time. The UI= code is clearer because it doesn't need to be re-entered from the top = each frame. This suits the declarative style of GUI specification, like the= example you gave, since we don't need special state or messages to com= municate what the GUI is doing. GUI state becomes a property of the... well= , the stack. It's the current scope of the code.

Hope this helps!

=A0-Tony


On Tue, Feb 14, 20= 12 at 11:02 AM, Anthony Tavener <anthony.tavener@gmail.com> wrote:
Apologies Philippe, this is a bit long...
The "update loop" I mentioned might be a bit of a= red-herring, as I'm only using that for continuously active UI element= s: such as changing cursor to represent the action which would be taken on = click. It has nothing to do with the basic UI flow.

I didn't understand delimcc right away, and I hardl= y understand it now! :)

I was looking to write UI = code much as your example of packing buttons together with directly bound a= ctivation functions.

Here's my "menu_of_list", which takes a l= ist of string * 'a pairs, calling GUI code to make a menu using the str= ings as labels, and using the 'a values as return values...

let menu_of_list lst return =3D
=A0 (* snipped: pr= eamble which handles positioning, stacking order, getting font, etc *)
=A0 Gui.add_widget gui
=A0 =A0 (new Gui.vbox pos stacking s= pacing
=A0 =A0 =A0 (lst |> List.map
=A0 =A0 =A0 =A0 (fun (label,= value) ->
=A0 =A0 =A0 =A0 =A0 Gui.wit_fn gui
=A0 =A0= =A0 =A0 =A0 =A0 (new Gui.rectangle_text w h fnt label)
=A0 =A0 = =A0 =A0 =A0 =A0 (fun () -> return (Some value) ) )))

The important part here is the "return" funct= ion. This will resume the UI coroutine. It is given to "menu_of_list&q= uot; by the UI code, then when the GUI has an activation of a menu button t= his "return" is called... resuming the UI where it left off, and = with the "Some value" which was associated to the button.

The magic of how this works is in delimcc capturing por= tions of the run stack. Here I've extracted the relevant bits of code:<= /div>


(* this runs as a coroutine with th= e "mainline" *)
let ui ui_process () =3D

=A0 (* in actual cod= e, this menu comes after a "right click", for special actions *)<= /div>
=A0 let act =3D yield ui_process (menu_of_list coord
=A0 =A0 [ ("Equip",`Equip);
=A0 =A0 =A0 ("Spell",`Spell);
=A0 =A0 =A0 ("E= nd",`End) ] ) in
=A0 (* ... handle whatever action the= user chose! *)


(* given SDL events= , calls activation functions of 'hit' widgets *)
let gui_react gui events =3D
=A0 let hits =3D gui_select gui= events in
=A0 match hits with
=A0 | h::t -> begin
=A0 =A0 match get_binding gui h with
=A0 =A0 | Some f -&= gt; f () =A0(* runs activation function of widget, such as "return&quo= t; to coroutine! *)
=A0 =A0 | None -> ()
=A0 =A0 end
=A0 | [] ->= ()

let _ =3D
=A0 (* A prompt is delimcc= 's mechanism for marking the stack to -limit- the
=A0 =A0* co= ntinuation, rather than creating whole-program continuations. *)
=A0 (* So here, the "ui" is initially run, and will *resume*= this mainline continuation
=A0 =A0* at the end of the "user= " function call. *)
=A0 let ui_prompt =3D new_prompt () in
=A0 push_prompt ui_prompt (ui ui_prompt);

=A0 ...<= /div>

=A0 (* in mainloop *)
=A0 =A0 gui_react = gui events;
=A0=A0
----------------------
Now I'm going to restate the yield function here, and try to explain...=

let yield level fn =3D
=A0 shift level (fun k ->
=A0 =A0 fn k;
=A0 =A0 ab= ort level () )

"shift" and "abort" are delimcc. This = runs the given function "fn" with a continuation 'k'... s= o this continuation is the "return" function passed to menu_of_li= st, and therefore bound to each menu-button. The abort exits the "ui c= oroutine", resuming the mainline, hence the name: yield.

The continuation 'k' comes from the shift... th= e continuation is the code "outside" of the shift call... so when= it's called (by the mainline's gui_react), it resume at the end of= 'yield' and keep on going... in this example, handling the returne= d action!

I hope this conveys at least the gist of what's goi= ng on... I read a lot of papers over-and-over, not understanding... althoug= h none were specifically GUI. Delimited continuations have numerous applica= tions and a surprising number of configurations for just a pair of function= s! (Abort is really a special case of "reset"... so it's shif= t and reset, in ump-teen configurations.)

I'll try to explain if you have any further questio= ns! However, I'm still trying to sort out how best to write my GUI code= -- there is a lot of room for improvement. :)

=A0-Tony

PS. I'd like to blame X. Leroy ;)... = for a post some 10 years ago replying to someone's attempt to do a GUI = in OCaml... Xavier casually replied something like "oh, you can use co= routines". That was a wild goose chase (or Dahu?)! Delimcc didn't = exist at the time, and it's native-code implementation came about just = a few years ago (thank-you Oleg!). Even then, I had no idea how I was suppo= sed to use a coroutine to write GUI code! Oh well, it was an adventure, and= I still don't know if this is a good thing, but I like it a lot more t= han "signals and slots" -- the usual scattered GUI code which is = connected by messages/events.


On Tue, Feb 14, 20= 12 at 3:17 AM, Philippe Veber <philippe.veber@gmail.com> wrote:
Hi Anthony,

This looks interesting, however as I'm not very fami= liar with delimcc (that's a shame, I admit), I fail to understand the f= low of the program. Would you mind giving a snippet of the update loop you = mentionned?
=A0
So far, I'm not sure how well this works= out for a complete project. I like it so far, but I have complexity growin= g in some "update loop" stuff, which are little closures I add to= be run each frame-update for reacting to mouse-over/hover.



Good luck in th= e hunt for elegant UI (code)!

Let's hope I'm not just Dahu hunt= ing!
(h= ttp://en.wikipedia.org/wiki/Dahu)

=A0

=A0Tony




--0016e6d64762933ceb04b8f9678f-- From mboxrd@z Thu Jan 1 00:00:00 1970 X-Sympa-To: caml-list@inria.fr 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 q1MBwBEa024636 for ; Wed, 22 Feb 2012 12:58:11 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AnUCAAfXRE/RVdy2mGdsb2JhbABDqU0BiQ0IIgEBAQEBCAkNBxQngXMBAQEDARICExkBGx0BAwELBgULDS4hAQERAQUBHAYBEiKHXwmaOgqLcoJxhTo/iHMCBQuIdGSCZQEECgEBAQoIWgaFDiFKBiaDMASVOIcXhACDFj2EBIFT X-IronPort-AV: E=Sophos;i="4.73,463,1325458800"; d="scan'208";a="132428885" Received: from mail-vx0-f182.google.com ([209.85.220.182]) by mail4-smtp-sop.national.inria.fr with ESMTP/TLS/RC4-SHA; 22 Feb 2012 12:58:05 +0100 Received: by vcmm1 with SMTP id m1so9318109vcm.27 for ; Wed, 22 Feb 2012 03:58:04 -0800 (PST) Received-SPF: pass (google.com: domain of philippe.veber@gmail.com designates 10.52.76.164 as permitted sender) client-ip=10.52.76.164; Authentication-Results: mr.google.com; spf=pass (google.com: domain of philippe.veber@gmail.com designates 10.52.76.164 as permitted sender) smtp.mail=philippe.veber@gmail.com; dkim=pass header.i=philippe.veber@gmail.com Received: from mr.google.com ([10.52.76.164]) by 10.52.76.164 with SMTP id l4mr14270618vdw.6.1329911884305 (num_hops = 1); Wed, 22 Feb 2012 03:58:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-type; bh=uPohWLf57b/G+ZrR4qcScF8/8Jus1R8GusLCNBI6iZo=; b=GAUNoO4smeaGLu02qC/WgEpctJ3QEpGcuQJ//H1qIfVDKENpnvRdtoMiFVstXL8aO/ N8Afz3T/7W4AVXYs8gnxPvbVyYwfJDroAAeqjKtPzXhUFYmDlCRVzNaPvHSM2Cy+hDqW hiRtuBiRAErSlTo3tK4P006UUou392U4T9SwM= Received: by 10.52.76.164 with SMTP id l4mr11555507vdw.6.1329911884237; Wed, 22 Feb 2012 03:58:04 -0800 (PST) MIME-Version: 1.0 Received: by 10.52.112.201 with HTTP; Wed, 22 Feb 2012 03:57:44 -0800 (PST) In-Reply-To: References: From: Philippe Veber Date: Wed, 22 Feb 2012 12:57:44 +0100 Message-ID: To: Anthony Tavener , Adrien Cc: caml users Content-Type: multipart/alternative; boundary=bcaec50166d3714ccc04b98c3d84 X-Validation-by: philippe.veber@gmail.com Subject: Re: [Caml-list] Functional GUI programming: looking for good practices --bcaec50166d3714ccc04b98c3d84 Content-Type: text/plain; charset=ISO-8859-1 Thank you very much Anthony and Adrien, your comments helped me a lot, I now have plenty of material to think on! Best, ph. 2012/2/15 Anthony Tavener > Hrm... when I re-read my prior post before sending, it made sense. Several > hours later it seems inadequate, and I've thought of something to say more > clearly... > > The execution of code bounces between the UI and the mainline. When the > mainline processes a "gui hit" it will resume the UI code *right where it > left off (yielded)*... then the UI will do something and get to another > point where it yields, awaiting input and thereby resuming the mainline > where it was (back to processing gui hits). > > Why would I want this? So that I don't have stateful UI code which has to > trickle down to the right place all the time. The UI code is clearer > because it doesn't need to be re-entered from the top each frame. This > suits the declarative style of GUI specification, like the example you > gave, since we don't need special state or messages to communicate what the > GUI is doing. GUI state becomes a property of the... well, the stack. It's > the current scope of the code. > > Hope this helps! > > -Tony > > > On Tue, Feb 14, 2012 at 11:02 AM, Anthony Tavener < > anthony.tavener@gmail.com> wrote: > >> Apologies Philippe, this is a bit long... >> >> The "update loop" I mentioned might be a bit of a red-herring, as I'm >> only using that for continuously active UI elements: such as changing >> cursor to represent the action which would be taken on click. It has >> nothing to do with the basic UI flow. >> >> I didn't understand delimcc right away, and I hardly understand it now! :) >> >> I was looking to write UI code much as your example of packing buttons >> together with directly bound activation functions. >> >> Here's my "menu_of_list", which takes a list of string * 'a pairs, >> calling GUI code to make a menu using the strings as labels, and using the >> 'a values as return values... >> >> let menu_of_list lst return = >> (* snipped: preamble which handles positioning, stacking order, getting >> font, etc *) >> Gui.add_widget gui >> (new Gui.vbox pos stacking spacing >> (lst |> List.map >> (fun (label,value) -> >> Gui.wit_fn gui >> (new Gui.rectangle_text w h fnt label) >> (fun () -> return (Some value) ) ))) >> >> The important part here is the "return" function. This will resume the UI >> coroutine. It is given to "menu_of_list" by the UI code, then when the GUI >> has an activation of a menu button this "return" is called... resuming the >> UI where it left off, and with the "Some value" which was associated to the >> button. >> >> The magic of how this works is in delimcc capturing portions of the run >> stack. Here I've extracted the relevant bits of code: >> >> >> (* this runs as a coroutine with the "mainline" *) >> let ui ui_process () = >> >> (* in actual code, this menu comes after a "right click", for special >> actions *) >> let act = yield ui_process (menu_of_list coord >> [ ("Equip",`Equip); >> ("Spell",`Spell); >> ("End",`End) ] ) in >> (* ... handle whatever action the user chose! *) >> >> >> (* given SDL events, calls activation functions of 'hit' widgets *) >> let gui_react gui events = >> let hits = gui_select gui events in >> match hits with >> | h::t -> begin >> match get_binding gui h with >> | Some f -> f () (* runs activation function of widget, such as >> "return" to coroutine! *) >> | None -> () >> end >> | [] -> () >> >> let _ = >> (* A prompt is delimcc's mechanism for marking the stack to -limit- the >> * continuation, rather than creating whole-program continuations. *) >> (* So here, the "ui" is initially run, and will *resume* this mainline >> continuation >> * at the end of the "user" function call. *) >> let ui_prompt = new_prompt () in >> push_prompt ui_prompt (ui ui_prompt); >> >> ... >> >> (* in mainloop *) >> gui_react gui events; >> >> ---------------------- >> >> Now I'm going to restate the yield function here, and try to explain... >> >> let yield level fn = >> shift level (fun k -> >> fn k; >> abort level () ) >> >> "shift" and "abort" are delimcc. This runs the given function "fn" with a >> continuation 'k'... so this continuation is the "return" function passed to >> menu_of_list, and therefore bound to each menu-button. The abort exits the >> "ui coroutine", resuming the mainline, hence the name: yield. >> >> The continuation 'k' comes from the shift... the continuation is the code >> "outside" of the shift call... so when it's called (by the mainline's >> gui_react), it resume at the end of 'yield' and keep on going... in this >> example, handling the returned action! >> >> I hope this conveys at least the gist of what's going on... I read a lot >> of papers over-and-over, not understanding... although none were >> specifically GUI. Delimited continuations have numerous applications and a >> surprising number of configurations for just a pair of functions! (Abort is >> really a special case of "reset"... so it's shift and reset, in ump-teen >> configurations.) >> >> I'll try to explain if you have any further questions! However, I'm still >> trying to sort out how best to write my GUI code -- there is a lot of room >> for improvement. :) >> >> -Tony >> >> PS. I'd like to blame X. Leroy ;)... for a post some 10 years ago >> replying to someone's attempt to do a GUI in OCaml... Xavier casually >> replied something like "oh, you can use coroutines". That was a wild goose >> chase (or Dahu?)! Delimcc didn't exist at the time, and it's native-code >> implementation came about just a few years ago (thank-you Oleg!). Even >> then, I had no idea how I was supposed to use a coroutine to write GUI >> code! Oh well, it was an adventure, and I still don't know if this is a >> good thing, but I like it a lot more than "signals and slots" -- the usual >> scattered GUI code which is connected by messages/events. >> >> >> On Tue, Feb 14, 2012 at 3:17 AM, Philippe Veber > > wrote: >> >>> Hi Anthony, >>> >>> This looks interesting, however as I'm not very familiar with delimcc >>> (that's a shame, I admit), I fail to understand the flow of the program. >>> Would you mind giving a snippet of the update loop you mentionned? >>> >>> >>>> So far, I'm not sure how well this works out for a complete project. I >>>> like it so far, but I have complexity growing in some "update loop" stuff, >>>> which are little closures I add to be run each frame-update for reacting to >>>> mouse-over/hover. >>>> >>>> >>> >>> Good luck in the hunt for elegant UI (code)! >>>> >>> >>> Let's hope I'm not just Dahu hunting! >>> (http://en.wikipedia.org/wiki/Dahu) >>> >>> >>> >>>> >>>> Tony >>>> >>>> >>> >> > --bcaec50166d3714ccc04b98c3d84 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Thank you very much Anthony and Adrien, your comments helped me a lot, I no= w have plenty of material to think on!
Best,
=A0 ph.

2012/2/15 Anthony Tavener <anthony.tavener@gmail.com>
Hrm... when I re-read my prior post before s= ending, it made sense. Several hours later it seems inadequate, and I'v= e thought of something to say more clearly...

The execution of code bounces between the UI and the mainlin= e. When the mainline processes a "gui hit" it will resume the UI = code *right where it left off (yielded)*... then the UI will do something a= nd get to another point where it yields, awaiting input and thereby resumin= g the mainline where it was (back to processing gui hits).

Why would I want this? So that I don't have statefu= l UI code which has to trickle down to the right place all the time. The UI= code is clearer because it doesn't need to be re-entered from the top = each frame. This suits the declarative style of GUI specification, like the= example you gave, since we don't need special state or messages to com= municate what the GUI is doing. GUI state becomes a property of the... well= , the stack. It's the current scope of the code.

Hope this helps!

=A0-Tony


On Tue, Feb 14, 2012 at 11:02 AM, Anthony Tavener <anthony.tavener@gmail.com> wrote:
Apologies Philippe, this is a bit long...
The "update loop" I mentioned might be a bit of a= red-herring, as I'm only using that for continuously active UI element= s: such as changing cursor to represent the action which would be taken on = click. It has nothing to do with the basic UI flow.

I didn't understand delimcc right away, and I hardl= y understand it now! :)

I was looking to write UI = code much as your example of packing buttons together with directly bound a= ctivation functions.

Here's my "menu_of_list", which takes a l= ist of string * 'a pairs, calling GUI code to make a menu using the str= ings as labels, and using the 'a values as return values...

let menu_of_list lst return =3D
=A0 (* snipped: pr= eamble which handles positioning, stacking order, getting font, etc *)
=A0 Gui.add_widget gui
=A0 =A0 (new Gui.vbox pos stacking s= pacing
=A0 =A0 =A0 (lst |> List.map
=A0 =A0 =A0 =A0 (fun (label,= value) ->
=A0 =A0 =A0 =A0 =A0 Gui.wit_fn gui
=A0 =A0= =A0 =A0 =A0 =A0 (new Gui.rectangle_text w h fnt label)
=A0 =A0 = =A0 =A0 =A0 =A0 (fun () -> return (Some value) ) )))

The important part here is the "return" funct= ion. This will resume the UI coroutine. It is given to "menu_of_list&q= uot; by the UI code, then when the GUI has an activation of a menu button t= his "return" is called... resuming the UI where it left off, and = with the "Some value" which was associated to the button.

The magic of how this works is in delimcc capturing por= tions of the run stack. Here I've extracted the relevant bits of code:<= /div>


(* this runs as a coroutine with th= e "mainline" *)
let ui ui_process () =3D

=A0 (* in actual cod= e, this menu comes after a "right click", for special actions *)<= /div>
=A0 let act =3D yield ui_process (menu_of_list coord
=A0 =A0 [ ("Equip",`Equip);
=A0 =A0 =A0 ("Spell",`Spell);
=A0 =A0 =A0 ("E= nd",`End) ] ) in
=A0 (* ... handle whatever action the= user chose! *)


(* given SDL events= , calls activation functions of 'hit' widgets *)
let gui_react gui events =3D
=A0 let hits =3D gui_select gui= events in
=A0 match hits with
=A0 | h::t -> begin
=A0 =A0 match get_binding gui h with
=A0 =A0 | Some f -&= gt; f () =A0(* runs activation function of widget, such as "return&quo= t; to coroutine! *)
=A0 =A0 | None -> ()
=A0 =A0 end
=A0 | [] ->= ()

let _ =3D
=A0 (* A prompt is delimcc= 's mechanism for marking the stack to -limit- the
=A0 =A0* co= ntinuation, rather than creating whole-program continuations. *)
=A0 (* So here, the "ui" is initially run, and will *resume*= this mainline continuation
=A0 =A0* at the end of the "user= " function call. *)
=A0 let ui_prompt =3D new_prompt () in
=A0 push_prompt ui_prompt (ui ui_prompt);

=A0 ...<= /div>

=A0 (* in mainloop *)
=A0 =A0 gui_react = gui events;
=A0=A0
----------------------
Now I'm going to restate the yield function here, and try to explain...=

let yield level fn =3D
=A0 shift l= evel (fun k ->
=A0 =A0 fn k;
=A0 =A0 abort level () = )

"shift" and "abort" are delimcc. This = runs the given function "fn" with a continuation 'k'... s= o this continuation is the "return" function passed to menu_of_li= st, and therefore bound to each menu-button. The abort exits the "ui c= oroutine", resuming the mainline, hence the name: yield.

The continuation 'k' comes from the shift... th= e continuation is the code "outside" of the shift call... so when= it's called (by the mainline's gui_react), it resume at the end of= 'yield' and keep on going... in this example, handling the returne= d action!

I hope this conveys at least the gist of what's goi= ng on... I read a lot of papers over-and-over, not understanding... althoug= h none were specifically GUI. Delimited continuations have numerous applica= tions and a surprising number of configurations for just a pair of function= s! (Abort is really a special case of "reset"... so it's shif= t and reset, in ump-teen configurations.)

I'll try to explain if you have any further questio= ns! However, I'm still trying to sort out how best to write my GUI code= -- there is a lot of room for improvement. :)

=A0-Tony

PS. I'd like to blame X. Leroy ;)... = for a post some 10 years ago replying to someone's attempt to do a GUI = in OCaml... Xavier casually replied something like "oh, you can use co= routines". That was a wild goose chase (or Dahu?)! Delimcc didn't = exist at the time, and it's native-code implementation came about just = a few years ago (thank-you Oleg!). Even then, I had no idea how I was suppo= sed to use a coroutine to write GUI code! Oh well, it was an adventure, and= I still don't know if this is a good thing, but I like it a lot more t= han "signals and slots" -- the usual scattered GUI code which is = connected by messages/events.


On Tue, Feb 14, 20= 12 at 3:17 AM, Philippe Veber <philippe.veber@gmail.com> wrote:
Hi Anthony,

This looks interesting, however as I'm not very fami= liar with delimcc (that's a shame, I admit), I fail to understand the f= low of the program. Would you mind giving a snippet of the update loop you = mentionned?
=A0
So far, I'm not sure how well this works= out for a complete project. I like it so far, but I have complexity growin= g in some "update loop" stuff, which are little closures I add to= be run each frame-update for reacting to mouse-over/hover.



Good luck in th= e hunt for elegant UI (code)!

Let's hope I'm not just Dahu hunt= ing!
(h= ttp://en.wikipedia.org/wiki/Dahu)

=A0

=A0Tony





--bcaec50166d3714ccc04b98c3d84-- 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