From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Original-To: caml-list@yquem.inria.fr Delivered-To: caml-list@yquem.inria.fr Received: from mail4-relais-sop.national.inria.fr (mail4-relais-sop.national.inria.fr [192.134.164.105]) by yquem.inria.fr (Postfix) with ESMTP id EE8A4BC37 for ; Wed, 9 Dec 2009 17:44:00 +0100 (CET) X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AjQBANtiH0tKfU4ZkGdsb2JhbACEJpZ7PwEBAQEJCQwHEwOrLYFZhVyIaQECAwWBKn2BLVMEgnwbiA8 X-IronPort-AV: E=Sophos;i="4.47,369,1257116400"; d="scan'208";a="51807875" Received: from ey-out-2122.google.com ([74.125.78.25]) by mail4-smtp-sop.national.inria.fr with ESMTP; 09 Dec 2009 17:43:59 +0100 Received: by ey-out-2122.google.com with SMTP id 22so1513061eye.7 for ; Wed, 09 Dec 2009 08:43:59 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:sender:received:date :x-google-sender-auth:message-id:subject:from:to:cc:content-type; bh=KmwsfpurDdOTFOXStJC+G0xn2htTG9ZeIDrsLVR60J4=; b=OjuTGICMyUPVh/0Z51isLTaPRAIWqagLts4elnYDuf1scf3v4gXMtjaPx89mvDzhgB EetkNuZcj8tcVcsvdCcDjyoR4BeOucaLQkNATv6BeXGHSU5dtm/VlK8MItUUah8DOKw2 kc55kCcvHWY3KZ4VoWdalCZrBOEunh0nwieuQ= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:sender:date:x-google-sender-auth:message-id:subject :from:to:cc:content-type; b=SoCsdCMK2jC54x0LlMLQv6ZyI9nqGF5dsbvgCZqTOF7OHGydeJRuM6JkkUiYGMZ6Z9 1yQ5Pf2A9muQHcStLW9QL0PLV5a9+C3B9yOdPnAineaH8OXYyK2FPms+bTFSXBLdlS9c ZEGCVlc4y7oK9UIm3GvjuUJudUxtdwdwGmHZw= MIME-Version: 1.0 Sender: daniel.c.buenzli@gmail.com Received: by 10.213.96.202 with SMTP id i10mr1161500ebn.99.1260377038955; Wed, 09 Dec 2009 08:43:58 -0800 (PST) Date: Thu, 10 Dec 2009 00:43:58 +0800 X-Google-Sender-Auth: fbe0c37a2aaa2503 Message-ID: <91a3da520912090843n451b5592x27105b353ef7c65b@mail.gmail.com> Subject: more on frp (was Re: [Caml-list] Re: Recursion on React.events) From: =?UTF-8?Q?Daniel_B=C3=BCnzli?= To: Richard Jones Cc: OCaml List Content-Type: text/plain; charset=UTF-8 X-Spam: no; 0.00; recursion:01 buenzli:01 haskell:01 manipulates:01 occurences:01 occurence:01 slider:01 slider:01 mutable:01 occurences:01 combinators:01 combinators:01 corresponds:01 occurence:01 combinator:01 > Personally I've yet to read any comprehensible introduction to FRP. I agree my documentation can be dry for a newcomer to frp. It's not a tutorial about frp. Its aim is to answer any questions a proficient user of the library could have and I hope that it fulfilths at least that goal. But frp itself is not that hard to comprehend. Unfortunately the only things I can point you to is (haskell & scheme) academic papers which also tend to quickly talk about (nonetheless interesting) semantic and implementation issues. Maybe the following along with react's documentation can help you to get started. It's far from perfect, it was quickly written, just a try. * Events and signals Frp manipulates two kinds of values. Events and signals (the latter are a.k.a. behaviours in many implementations). Events and signals are values that depend on time. An event has values (occurences) at specific points in time. For example it could represent the keysym of the key depressed by a user. Events can be understood as follows : type 'a event = time -> 'a option Applying the function with the current time tells you whether there's an event occurence or not at that time. In our example whether a key was depressed or not. A signal has a value at any point in time. It represents values that vary continuously like the current value of a slider. The type for signals can be seen as type 'a signal = time -> 'a Applying the function with the current time tells you the current value of the signal. In our example it tells you the current value of the slider. In general, state, i.e. anything that you'd fit in a reference cell or a mutable field is a candidate to become a signal. As you are an astute reader you will already have remarked that the following approach could have been taken : type 'a event = 'a option signal In fact events and signals are duals. One can be implemented in terms of the other (signals can be seen as the event occurences of its value changes), However it makes sense to keep the two concepts separate (code clarity, efficiency issues, semantic details with comparison). Now given events or signals, frp provides you combinators to define new derived events. These combinators are in the modules React.E and React.S. For example suppose we are given the following event (for now don't bother about how it could be defined) : let keydown : keysym event = ... which represents keysyms of the keys depressed by a user. We would like to define an event that corresponds to the character of the keysym. We have the following translation function : let char_of_keysym : keysym -> char = fun ks -> ... So we can define our new event as : let keychar = E.map char_of_keysym keydown Every time keydown gets an occurence, keychar gets one. Except the value of the occurence for keychar is the value of keydown transformed by char_of_keysym. Suppose we want to remember the last keysym that was issued. That's a signal, it is defined at any point in time. For that the S.hold combinator can be used, it remembers the last occurence of an event : let last_keysym = S.hold null_keysym keydown Each time keydown gets an occurence S.hold updates the signal with the new occurence value. null_keysym is used to initalize the signal (it needs a value at the time it is created and the keydown event may not give us one at that time). Now we would like to concatenate the stream of keychar occurences into a string. That is we want a string signal that holds at any point in time the key characters that were depressed so far. It can be defined by folding over the sequence of keychar occurences : let istr = S.fold (fun acc c = acc ^ (String.make 1 c)) "" keychar So istr is a string signal. Its initial value is "". Each time keychar gets an occurence, the signal is updated by applying the folding function to the current value of the signal with the event occurence. The result of the folding function is the new value for the signal. Suppose we have two float signals a and b. We want to have a signal that keeps the sum of a and b at any time. For that we lift the (+.) function to act from float values to float signals as follows : let sum = S.l2 (+.) a b Now whenever a or b changes sum is automatically updated with the sum of the current value of a or b. To sum up event and signal combinators take signals and events arguments and return a new signal or event r. r is said to depend on the arguments given to the combinator. So effectively what you are building with combinators is a graph of data dependencies. Think about it like the dependencies in a spreadsheet (e.g. the sum example above). What frp gives you is that whenever a value changes in the graph all the transitive dependencies get automatically updated in a consistant way (glitches are avoided by updating all dependee before updating the dependent). Now the question is how can I update signal values and generate event occurences, combinators only take other events and signals, they don't allow to update values or generate occurences ? The answer is primitive events and signals. Before moving on. I should stress that the notion of time in react is purely relative (this happens before that). There's no concrete value of type time as suggested above, this notion of time is only used as a tool to define the denotational semantics of events and signals. Also realtime can be integrated if necessary (e.g. look rtime or the clock example in the distribution) but it is not necessary. You define what time means to you (e.g. it could be the sucession of server request, the sucession of mouse inputs, etc.). * Primitives The only events whose occurence can be generated by the client are primitive events. The only signals whose values can be updated by the client are primitive signals. All events and signals that result from combinators are automatically updated by react. Primitives are the mechanism to interface the reactive runtime described above with the outside world (e.g. to define a keysym event). Read now the "Primitive events and signals" section and come back after ; http://erratique.ch/software/react/doc/React#primitives * Program structure If you own your main loop I'd say you should more or less proceed as follows. 1) Identify your primitives (sources of input). Define functions to create primitives. Or if there's no dynamic creation of primitives create them directly. 2) Define your reactive system in terms of primitive events and combinators. Usually at the end of your dataflow graph you will have effectful events and signals to give feedback to the world (beware, primitive sending and update functions cannot not be used here to feedback into the reactive system, depose the updates in a queue if you really need to do that, but more likely you can use fix point combinators to avoid that). 3) Define an infinite loop that sources the created primitives as input is gathered. 4) Run the loop. Have a look at the snippet of code at the end of this email. It exposes the a subset of the sdl mouse as signals and events (note that there's more than one way of exposing that information as a set of events and signals, this may not be the best solution). If you need to deal with callbacks then the result of callbacks can be exposed as events and signals by creating a primitive and using the primitive sending or updating function in the callback. I now suggest you try to proceed to the "Basics" section of the documentation http://erratique.ch/software/react/doc/React#basics After have a look at the "Semantics" http://erratique.ch/software/react/doc/React#sem You may also have a look at the breakout.ml example of the distribution (use the version in the repository there are fixes for xterm & vte based terminals). http://erratique.ch/software/react/repo/test/breakout.ml > I'm interested in whether FRP can be used to write Gtk interfaces with > reduced code complexity. Apparently it can, but I've no idea how. Once you are more acquainted with the subject. Have a look at the chapter 6 of Gregory Cooper's thesis [1] it provides strategies to interface frp with existing object-oriented gui toolkits. Chapter 1 may also be interesting in motivating the frp approach. Finally, feel free to ask any question, Best, Daniel [1] http://www.cs.brown.edu/~greg/thesis.pdf open React;; module Mouse : sig val pos : (int * int) S.t (* mouse position signal. *) val up : unit E.t (* event on mouse button up. *) val down : unit E.t (* event on mouse button down. *) val input : unit -> unit (* inputs mouse changes until user quits. *) end = struct let pos, set_pos = S.create (0, 0) let down, send_down = E.create () let up, send_up = E.create () open Sdlevent;; let rec input () = match wait_event () with | QUIT -> () | MOUSEMOTION e -> set_pos (e.mme_x, e.mme_y); input () | MOUSEBUTTONDOWN e | MOUSEBUTTONUP e when e.mbe_button = Sdlmouse.BUTTON_LEFT -> (match e.mbe_state with PRESSED -> send_down () | RELEASED -> send_up ()); input () | KEYDOWN k when k.keycode = 'q' -> () | _ -> input () end