Zélus provides valued signals that are built and accessed, respectively, through the constructions emit and present. At every instant, a valued signal is a pair comprising (1) a boolean c indicating when the signal is present and (2) a value that is present when c is true.3
Unlike shared variables, signal values are not necessarily defined at every instant, nor do they implicitly keep their previous value when not updated. Consider this program, for instance:
let node within (min, max, x) = o where rec c = (min <= x) & (x <= max) and present c -> do emit o = () done
It computes a condition c based on the input x. The signal o is present with value () every time c is true. There is no need to give an initial value for o. When c is false, o is simply absent. Removing the emit gives a program that the compiler rejects:
let node within (min, max, x) = o where rec c = (min <= x) & (x <= max) and present c -> do o = () done
The output o is not declared as a shared variable (with init) nor is it defined as a signal (with emit).
The presence of a signal expression e can be tested by the boolean expression ?e. The following program, for example, counts the number of instants where x is emitted.
let node count x = cpt where rec cpt = if ?x then 1 -> pre cpt + 1 else 0 -> pre cpt
There is also a more general mechanism to test signal presence that treats multiple signals simultaneously and allows access to their values. It resembles the pattern-matching construct of ML and it only allows signal values to be accessed at instants of emission.4
The program below has two input signals, x and y, and returns the sum of their values when both are emitted, the value of x when it alone is emitted, the value of y when it alone is emitted, and 0 otherwise.
let node sum (x, y) = o where present | x(v) & y(w) -> do o = v + w done | x(v) -> do o = v done | y(w) -> do o = w done else do o = 0 done end
A present statement comprises several signal patterns and handlers. The patterns are tested sequentially from top to bottom. The signal condition x(v) & y(w) matches when both x and y are present. The condition x(v) means “x is present and has some value v”. When x is present, the variable v is bound to its value in the corresponding handler.
In the signal pattern x(v) & y(w), x and y are expressions that evaluate to signal values and v and w are patterns. Writing x(42) & y(w) means “detect the presence of signal x with value 42 and the simultaneous presence of y”.
The output of the preceding function is a regular stream since the test is exhaustive thanks to the else clause. Omitting this default case results in an error.
let node sum (x, y) = o where present | x(v) & y(w) -> do o = v + w done | x(v1) -> do o = v1 done | y(v2) -> do o = v2 done end
This error is easily eliminated by giving a last value to o—for example, by adding the equation init o = 0 outside the present statement. The default case is then implicitly completed with o = last o. Another way is to state that o is a signal and thus only partially defined.
let node sum (x, y) = o where present | x(v) & y(w) -> do emit o = v + w done | x(v1) -> do emit o = v1 done | y(v2) -> do emit o = v2 done end
Signal patterns may also contain boolean expressions. The following program adds the values of the two signals x and y if they are emitted at the same time and if z >= 0.
let node sum (x, y, z) = o where present x(v) & y(w) & (z >= 0) -> do o = v + w done else do o = 0 done end
Remark: Signals make it possible to mimic the default construction of the language Signal . Signal’s default x y takes the value of x when x is present and the value of y when x is absent and y is present. The signal pattern x(v) | y(v) tests the presence of “x or y”.
let node signal_default (x, y) = o where present x(v) | y(v) -> do emit o = v done end
This is only a simulation of Signal’s behavior since all information about the instants where x and y are present—the so-called clock calculus of Signal —is hidden at run-time and not exploited by the compiler. In particular, and unlike in the clock calculus of Signal, the Zélus compiler cannot determine that o is emitted only when x or y are present.
Unlike Lustre, Lucid Synchrone and Signal, Zélus does not currently have a clock calculus.