caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] Functional GUI programming: looking for good practices
@ 2012-02-13 11:01 Philippe Veber
  2012-02-13 15:27 ` Adrien
  2012-02-13 18:13 ` Raoul Duke
  0 siblings, 2 replies; 15+ messages in thread
From: Philippe Veber @ 2012-02-13 11:01 UTC (permalink / raw)
  To: caml users

[-- Attachment #1: Type: text/plain, Size: 924 bytes --]

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.

[-- Attachment #2: Type: text/html, Size: 1022 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-13 11:01 [Caml-list] Functional GUI programming: looking for good practices Philippe Veber
@ 2012-02-13 15:27 ` Adrien
  2012-02-14 10:02   ` Philippe Veber
  2012-02-13 18:13 ` Raoul Duke
  1 sibling, 1 reply; 15+ messages in thread
From: Adrien @ 2012-02-13 15:27 UTC (permalink / raw)
  To: Philippe Veber; +Cc: caml users

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

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-13 11:01 [Caml-list] Functional GUI programming: looking for good practices Philippe Veber
  2012-02-13 15:27 ` Adrien
@ 2012-02-13 18:13 ` Raoul Duke
  2012-02-14  0:36   ` Anthony Tavener
  1 sibling, 1 reply; 15+ messages in thread
From: Raoul Duke @ 2012-02-13 18:13 UTC (permalink / raw)
  To: Philippe Veber; +Cc: caml users

On Mon, Feb 13, 2012 at 3:01 AM, Philippe Veber
<philippe.veber@gmail.com> 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

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-13 18:13 ` Raoul Duke
@ 2012-02-14  0:36   ` Anthony Tavener
  2012-02-14 10:17     ` Philippe Veber
  0 siblings, 1 reply; 15+ messages in thread
From: Anthony Tavener @ 2012-02-14  0:36 UTC (permalink / raw)
  To: Raoul Duke; +Cc: Philippe Veber, caml users

[-- Attachment #1: Type: text/plain, Size: 2993 bytes --]

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 <raould@gmail.com> wrote:

> On Mon, Feb 13, 2012 at 3:01 AM, Philippe Veber
> <philippe.veber@gmail.com> 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
>
>

[-- Attachment #2: Type: text/html, Size: 4334 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-13 15:27 ` Adrien
@ 2012-02-14 10:02   ` Philippe Veber
  2012-02-14 10:21     ` Daniel Bünzli
  2012-02-14 13:29     ` Adrien
  0 siblings, 2 replies; 15+ messages in thread
From: Philippe Veber @ 2012-02-14 10:02 UTC (permalink / raw)
  To: Adrien; +Cc: caml users

[-- Attachment #1: Type: text/plain, Size: 5797 bytes --]

2012/2/13 Adrien <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 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
>

[-- Attachment #2: Type: text/html, Size: 7644 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14  0:36   ` Anthony Tavener
@ 2012-02-14 10:17     ` Philippe Veber
  2012-02-14 18:02       ` Anthony Tavener
  0 siblings, 1 reply; 15+ messages in thread
From: Philippe Veber @ 2012-02-14 10:17 UTC (permalink / raw)
  To: Anthony Tavener; +Cc: Raoul Duke, caml users

[-- Attachment #1: Type: text/plain, Size: 617 bytes --]

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

[-- Attachment #2: Type: text/html, Size: 1302 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14 10:02   ` Philippe Veber
@ 2012-02-14 10:21     ` Daniel Bünzli
  2012-02-14 10:39       ` Philippe Veber
  2012-02-14 13:29     ` Adrien
  1 sibling, 1 reply; 15+ messages in thread
From: Daniel Bünzli @ 2012-02-14 10:21 UTC (permalink / raw)
  To: Philippe Veber; +Cc: caml users

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


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14 10:21     ` Daniel Bünzli
@ 2012-02-14 10:39       ` Philippe Veber
  2012-02-14 11:52         ` Adrien
  0 siblings, 1 reply; 15+ messages in thread
From: Philippe Veber @ 2012-02-14 10:39 UTC (permalink / raw)
  To: Daniel Bünzli; +Cc: caml users

[-- Attachment #1: Type: text/plain, Size: 1087 bytes --]

Hi Daniel,

2012/2/14 Daniel Bünzli <daniel.buenzli@erratique.ch>

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


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

[-- Attachment #2: Type: text/html, Size: 1760 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14 10:39       ` Philippe Veber
@ 2012-02-14 11:52         ` Adrien
  2012-02-14 13:00           ` Daniel Bünzli
  0 siblings, 1 reply; 15+ messages in thread
From: Adrien @ 2012-02-14 11:52 UTC (permalink / raw)
  To: Philippe Veber; +Cc: Daniel Bünzli, caml users

On 14/02/2012, Philippe Veber <philippe.veber@gmail.com> wrote:
> Hi Daniel,
>
> 2012/2/14 Daniel Bünzli <daniel.buenzli@erratique.ch>
>
>> 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


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14 11:52         ` Adrien
@ 2012-02-14 13:00           ` Daniel Bünzli
  0 siblings, 0 replies; 15+ messages in thread
From: Daniel Bünzli @ 2012-02-14 13:00 UTC (permalink / raw)
  To: Adrien; +Cc: Philippe Veber, caml users



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




^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14 10:02   ` Philippe Veber
  2012-02-14 10:21     ` Daniel Bünzli
@ 2012-02-14 13:29     ` Adrien
  1 sibling, 0 replies; 15+ messages in thread
From: Adrien @ 2012-02-14 13:29 UTC (permalink / raw)
  To: Philippe Veber; +Cc: caml users

On 14/02/2012, Philippe Veber <philippe.veber@gmail.com> wrote:
> 2012/2/13 Adrien <camaradetux@gmail.com>
>> 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

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14 10:17     ` Philippe Veber
@ 2012-02-14 18:02       ` Anthony Tavener
  2012-02-15  4:47         ` Anthony Tavener
  0 siblings, 1 reply; 15+ messages in thread
From: Anthony Tavener @ 2012-02-14 18:02 UTC (permalink / raw)
  To: Philippe Veber; +Cc: caml users

[-- Attachment #1: Type: text/plain, Size: 5188 bytes --]

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 <philippe.veber@gmail.com>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
>>
>>
>

[-- Attachment #2: Type: text/html, Size: 7460 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-14 18:02       ` Anthony Tavener
@ 2012-02-15  4:47         ` Anthony Tavener
  2012-02-22 11:57           ` Philippe Veber
  0 siblings, 1 reply; 15+ messages in thread
From: Anthony Tavener @ 2012-02-15  4:47 UTC (permalink / raw)
  To: Philippe Veber; +Cc: caml users

[-- Attachment #1: Type: text/plain, Size: 6509 bytes --]

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 <philippe.veber@gmail.com>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
>>>
>>>
>>
>

[-- Attachment #2: Type: text/html, Size: 9057 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-15  4:47         ` Anthony Tavener
@ 2012-02-22 11:57           ` Philippe Veber
  2012-02-28 10:10             ` Adrien
  0 siblings, 1 reply; 15+ messages in thread
From: Philippe Veber @ 2012-02-22 11:57 UTC (permalink / raw)
  To: Anthony Tavener, Adrien; +Cc: caml users

[-- Attachment #1: Type: text/plain, Size: 6891 bytes --]

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 <anthony.tavener@gmail.com>

> 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 <philippe.veber@gmail.com
>> > 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
>>>>
>>>>
>>>
>>
>

[-- Attachment #2: Type: text/html, Size: 9624 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [Caml-list] Functional GUI programming: looking for good practices
  2012-02-22 11:57           ` Philippe Veber
@ 2012-02-28 10:10             ` Adrien
  0 siblings, 0 replies; 15+ messages in thread
From: Adrien @ 2012-02-28 10:10 UTC (permalink / raw)
  To: Philippe Veber; +Cc: Anthony Tavener, caml users

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


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2012-02-28 10:10 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-02-13 11:01 [Caml-list] Functional GUI programming: looking for good practices Philippe Veber
2012-02-13 15:27 ` Adrien
2012-02-14 10:02   ` Philippe Veber
2012-02-14 10:21     ` Daniel Bünzli
2012-02-14 10:39       ` Philippe Veber
2012-02-14 11:52         ` Adrien
2012-02-14 13:00           ` Daniel Bünzli
2012-02-14 13:29     ` Adrien
2012-02-13 18:13 ` Raoul Duke
2012-02-14  0:36   ` Anthony Tavener
2012-02-14 10:17     ` Philippe Veber
2012-02-14 18:02       ` Anthony Tavener
2012-02-15  4:47         ` Anthony Tavener
2012-02-22 11:57           ` Philippe Veber
2012-02-28 10:10             ` Adrien

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).