caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: Guillaume Yziquel <guillaume.yziquel@citycable.ch>
To: "Daniel Bünzli" <daniel.buenzli@erratique.ch>
Cc: caml-list@inria.fr
Subject: Re: Recursion on React.events.
Date: Wed, 09 Dec 2009 19:47:57 +0100	[thread overview]
Message-ID: <4B1FF0DD.903@citycable.ch> (raw)
In-Reply-To: <91a3da520912082025mf4aba28o2b36884b3b001831@mail.gmail.com>

Daniel Bünzli a écrit :
>>> let rec regular_schedule start_time period =
>>>  React.E.switch React.E.never begin React.E.map
>>>    begin fun () -> regular_schedule (Calendar.add (Calendar.now ())
>>> period) period end
>>>    begin schedule start_time end
>>>  end
> 
> Anyway don't do any recursive tricks unless you really know what you
> are doing (which you don't seem). You are asking for trouble (infinite
> loops and puzzling behaviour more precisely). The ONLY right way to
> define recursive events and signals is to use the fixed point
> operators. So in your case something like this should work :
> 
> let regular_schedule start_time period =
>   let define tick = (* tick is the value of tick' dt times ago *)
>     let tick' =
>       let reschedule () = Calendar.add (Calendar.now ()) period in
>       React.E.switch (schedule start_time) (E.map reschedule tick)
>     in
>     tick', tick'
>   in
>   E.fix define

Thanks. This works perfectly! (I mean, with the tweak you mentioned in 
your second email).

I still do not understand why there's the couple (tick', tick') and not 
simply tick', nevertheless...

> Note that in general I would avoid what you are doing altoghether by
> providing regular_schedule as a primitive as you do for schedule. If
> you are using too much ugly side effects and tricks in your event
> definitions then you loose all the benefits of frp.

Well, I do not fully agree. While I agree that keeping code clear and 
non-confusing is the best option, I do not really know if I can avoiding 
doing such magic.

My use case is the following: I'm writing a scheduler that I hope to 
extend smartly over time. It's a scheduler that is based on Calendar, 
and that runs in a Lwt.thread. Source code is given below, at the end of 
this email. There's a schedule function that somehow registers a task in 
the scheduler, and returns a React.E.event on which code using the 
library can hook to.

Making a regular_schedule in the way you suggested proved to be quite 
difficult without changing the code of the scheduler itself, which I'd 
like to keep clean and small.

Moreover, I also aim to make a auto_schedule function that does some 
rescheduling at with a delay that is know at the time of rescheduling.

Generally speaking, I want to keep the scheduler small and clean, and 
give flexibility to the user of the scheduling library. So providing 
regular_schedule as a primitive does not seem to me to fit this perspective.

> Best,
> 
> Daniel
> 
> P.S. You may want to have a look at rtime : http://erratique.ch/software/rtime

Cool. One criticism and one question.

Criticism: it doesn't use Calendar, which really a cool library.

Question: How well does Rtime interact with Lwt?

I'll have a look at what I can do with it.

Here's the code of the scheduler:

> open Lwt
> open CalendarLib
> 
> type task = {
>   schedule : Calendar.t;
>   trigger  : unit -> unit; }
> 
> let compare t1 t2 =
>   let n = Calendar.Date.compare
>     (Calendar.to_date t1.schedule)
>     (Calendar.to_date t2.schedule) in
>   if n = 0 then CalendarLib.Calendar.Time.compare
>     (Calendar.to_time t1.schedule)
>     (Calendar.to_time t2.schedule)
>   else n
> 
> let tasks : task list ref = ref []
>
> let register_new_task t =
>   let rec aux = function | [] -> t::[] | hd::tl ->
>     begin match compare hd t with
>     | 1 -> t::hd::tl | _ -> hd::(aux tl) end
>   in tasks := aux !tasks
> 
> let (read_control_fd, write_control_fd) = Lwt_unix.pipe ()
> 
> let task_channel = ref None
> let task_mutex = Lwt_mutex.create ()
> 
> let _ =
>   let rec receive_order = let buffer_command = " " in begin fun () ->
>     match Unix.read (Lwt_unix.unix_file_descr read_control_fd) buffer_command 0 1 with
>     | 1 -> begin match !task_channel with
>            | Some new_task -> begin
>                task_channel := None;
>                Lwt_mutex.unlock task_mutex;
>                register_new_task new_task;
>                loop () end
>            | None -> assert false
>            end
>     | _ -> assert false end
>   and loop () : unit Lwt.t = match !tasks with
>     | [] -> Lwt_unix.wait_read read_control_fd >>= fun () -> receive_order ()
>     | hd::tl -> let float_delay =
>         Calendar.Time.Second.to_float (
>         Calendar.Time.Period.to_seconds (
>         Calendar.Period.to_time (
>         Calendar.sub hd.schedule (
>         Calendar.now ())))) in
>         begin match float_delay > 0. with
>         | false -> tasks := tl; hd.trigger (); loop ()
>         | true -> Lwt.catch begin fun () ->
>                     Lwt_unix.with_timeout float_delay begin function () ->
>                       Lwt_unix.wait_read read_control_fd >>= fun () ->
>                       receive_order () end
>                   end begin function
>                   | Lwt_unix.Timeout -> tasks := tl; hd.trigger (); loop ()
>                   | _ -> assert false
>                   end end in
>   loop ()
> 
> let schedule date =
>   let aux () =
>     let (e, set_e) = React.E.create () in
>     Lwt_mutex.lock task_mutex >>= fun () ->
>     task_channel := Some {schedule = date; trigger = set_e;};
>     Lwt_unix.write write_control_fd "X" 0 1 >>= function
>     | 1 -> Lwt.return e | _ -> assert false in
>   Lwt_main.run (aux ())
> 
> let regular_schedule start period =
>   (* The let define tick in E.fix define is the proper way to
>      implement recursive events. define has type React.event ->
>      (React.event * React.event) and its argument is a placeholder
>      for the event at time t-dt. *)
>   let define tick =
>     let tick' = React.E.switch (schedule start) (React.E.map begin function () ->
>       schedule (Calendar.add (Calendar.now ()) period) end tick) in
>     tick', tick'
>   in
>   React.E.fix define

-- 
      Guillaume Yziquel
http://yziquel.homelinux.org/


  reply	other threads:[~2009-12-09 18:47 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-12-09  2:40 Guillaume Yziquel
2009-12-09  3:22 ` Guillaume Yziquel
2009-12-09  4:25 ` Daniel Bünzli
2009-12-09 18:47   ` Guillaume Yziquel [this message]
2009-12-10  8:39     ` Daniel Bünzli
2009-12-09  7:53 ` Daniel Bünzli
2009-12-09 11:23   ` [Caml-list] " Richard Jones
2009-12-09 18:01     ` Guillaume Yziquel
2009-12-10  3:38       ` Daniel Bünzli
2009-12-10 22:24         ` Guillaume Yziquel
2009-12-11 12:16           ` Jérémie Dimino
2009-12-09 18:24   ` Guillaume Yziquel
2009-12-10  8:24     ` Daniel Bünzli
2009-12-10 21:41       ` Guillaume Yziquel
2009-12-11  1:22         ` Daniel Bünzli

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4B1FF0DD.903@citycable.ch \
    --to=guillaume.yziquel@citycable.ch \
    --cc=caml-list@inria.fr \
    --cc=daniel.buenzli@erratique.ch \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).