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