The language is a functional language: functions are first-class
objects which can be given to functions or returned by functions. For
it function feeds back a network (i.e, it iterates
a function on a stream of values). It graphical representation is
given in figure 1.6.
let node it f init x = y where rec y = f x (init fby y) val it : (’a -> ’b -> ’b) -> ’b -> ’a => ’b val it :: (’a -> ’b -> ’b) -> ’b -> ’a -> ’b
sum x = it (+) 0 x
Note that type signature of
it states that its argument
f is considered to be a combinatorial function. To make a (more
general) rewinding operator for a stateful function, one has to write
let node it f init x = y where rec y = run (f x) (init fby y) val it : (’a -> ’b => ’b) -> ’b -> ’a => ’b val it :: (’a -> ’b -> ’b) -> ’b -> ’a -> ’b
run keyword used in an expression states that its argument
is expected to be a stateful function. Thus,
run (f x)
f x must have some type t1 ⇒ t2
instead of t1 → t2.
Higher-order is a natural way to build new primitives from existing
ones. For example, the so-called “activation condition” is a
build-in operator in block-diagram design tools (à la
Scade/Lustre or Simulink). An activation condition takes a function
f, a clock
default value and an
input an computes
f(input when clk). It then sets the
result on the base clock.
let node cond_act f clk default input = let rec o = merge clk (run f (input when clk)) ((default fby o) whenot clk) in o node cond_act : (’a => ’b) -> bool -> ’b -> ’a -> ’b node cond_act :: (’a on _c0 -> ’a on _c0) -> (_c0:’a) -> ’a -> ’a -> ’a
cond_act construction, one can rewrite the
RisingEdgeRetrigger operator given in
section 1.2.2 as the following:
let node count_down (res, n) = cpt where rec cpt = if res then n else (n -> pre (cpt - 1)) let node rising_edge_retrigger rer_input number_of_cycle = rer_output where rec rer_output = (0 < v) & clk and v = cond_act count_down clk 0 (count, number_of_cycle) and c = false fby rer_output and clock clk = c or count and count = false -> (rer_input & pre (not rer_input))
The symmetric operation of the activation condition is an iterator which simulates an internal
loop, generalizing what has been done in paragraph 1.2.3.
This operator consists in iterating a function on an input.
let node iter clk init f input = (* read input when clk is true *) let rec i = merge clk input ((init fby i) whenot clk) in let rec o = f i po and po = merge clk (init when clk) ((init fby o) whenot clk) in (* emit output when clk is true *) o when clk val iter : clock -> ’a -> (’a -> ’a -> ’a) -> ’a => ’a val iter :: (_c0:’a) -> ’a -> (’a -> ’a -> ’a) -> ’a on _c0 -> ’a on _c0
iter clk init f input reads an
input every time
clk is true and computes
f. The computation takes place
at every instant (on clock
input is read
clk is true.
We can illustrate this operator on the computation of the power of a
x be some stream with clock
'a on clk such
that the number of instants between two successive true values of
clk is k. Now, write a program which computes the sequence
(yi)i ∈ IN such that y0 = 1 and yi+1 =
This program can be implemented in the following way:
let node power clk x = o where o = iter clk 1 (fun i acc -> i * acc) x let node power10 x = power ten x
Here are some typical combinators.
let node (||) f g x = (run f x, run g x) let node (>) f g x = run g (run f x) let clock half = h where rec h = true -> not (pre h) let node alternate f g x = merge half (run f (x when half)) (run g (x whenot half)) val || : (’a => ’b) -> (’a => ’c) -> ’a => ’b * ’c val || :: (’a -> ’b) -> (’a -> ’c) -> ’a -> ’b * ’c val > : (’a => ’b) -> (’b => ’c) -> ’a => ’c val > :: (’a -> ’b) -> (’b -> ’c) -> ’a -> ’c val half : bool val half :: ’a val alternate : (’a => ’b) -> (’a => ’b) -> ’a => ’b val alternate :: (’a on half -> ’b on half) -> (’a on not half -> ’b on not half) -> ’a -> ’b
The infix operator
(||) computes the product of two functions
(>) composes two
alternate alternates the execution of a function
with another one.
All the programs defined above still define reactive systems: programs are compiled into finite state machines answering in bounding time and space whatever be their input.
In the examples considered previously, function used as parameters do
not evolve during the execution. Intuitively, the
receives a stream function
f and instantiates it once.
The language provides some means to deal with streams of functions. This is strictly more expressive that the previous case and is a way to model reconfigurable systems.
The function application instantiates a function with some
argument. We can define a more general operator, say
reconfigure which expects a stream of function
argument and instantiates the current value of the code every time a
new code is received.
let node reconfigure code input = o where rec automaton Await -> do unless code(c) then Run(c) | Run(c) -> do emit o = c input unless code(c) then Run(c) end
We can make the example a little more complicated by bounding the time
c input. For example:
let node server code input money = o where automaton Await -> do unless code(c) & money(m) then Run(c,m) | Run(c,m) -> let rec cpt = m -> pre cpt - 1 in do emit o = c input until (cpt = 0) then Await end