Application Bisimilarity is useful to prove inductively the correctness of source-to-source transformations. E.g., (+)( 1 , 2 ) and 3, which do not have the same representation, are equivalent. E.g., it can be implemented as CoF ( λ ( s 1 , s 2 ) . (+)( 1 , 2 ) , ( s 1 , s 2 ) , ( () , () )) or CoF ( λ s . (+)( 1 , 2 ) , s , () ) or even CoF ( λ s . 3 , s , () ) . Yet, they compute the same sequences. Exercice: One you have seen the semantics of the language kernel, prove that 0 fby ( x + y ) and ( 0 fby x ) + ( 0 fby y ) are equivalent. 22 / 108
Combinatorial functions (again) In the kernel language, it is only possible to define a stateful stream function: let node f x = e The kernel can be extended to declare a function that must be combinatorial: let f x = e In that case, e must be typable with kind 0. The language has two forms of applications: f e and run f e . It is be possible to have one only, e.g., write run f e or simply f e . The type system can infer the sort k ′ of the application. 23 / 108
It would be possible to consider that all stream functions are implemented as stateful functions. When imported, a function f : T 1 → T 2 becomes a value: CoP ( λ s , v . ( f ( v ) , s ) , () ) : SFun ( T 1 , T 2 , Unit ) A function f is combinatorial if it is equivalent to a function of type SFun ( T 1 , T 2 , Unit ) . To highlight the fact that a combinatorial function can be implemented more simply than a stateful function, we prefer to keep the two form of applications. 3 3 In Scade 6 and Zelus, there is a single form of application. 24 / 108
Fixpoint Consider a synchronous stream function f : S → T → T × S . Write fix ( f ) : S → T × S for the smallest fix-point of f . That is, given an initial state s : S , we want fix ( f ) to be a solution of the following equation: X ( s ) = let v , s ′ = X ( s ) in f s v fix ( f )( s ) = v , s ′ such that: v , s ′ = f s v This fix-point can be can be implemented with a recursion on values, for example in Haskell: fix ( f ) = λ s . let rec v , s ′ = f v s in v , s ′ The value v is defined recursively. 25 / 108
Justification of its existence In order to apply the Kleene theorem that state the existence of a smallest fix-point, all functions must be total. Given a set of values V , V ⊥ = V + {⊥} extends V with an element ⊥ which represents an undefined value. ≤ is the flat order with ⊥ as the minimum element (that is, ∀ x ∈ V , ⊥ < x ). 4 Suppose that f : V 1 �→ V 2 is a external function, e.g., + , − , not that is total. We implicitly lift it into f : V ⊥ 1 �→ V ⊥ 2 so that f ( ⊥ ) = ⊥ . For the set of streams, the bottom element is the trivial step function λ s . ( ⊥ , s ) and the initial state is the bottom element ⊥ . Call ⊥ stream or simply ⊥ , this bottom stream CoF ( λ s . ( ⊥ , s ) , ⊥ ) . It corresponds to a stream process that stuck: giving an input state, it never returns a value. 4 ( x < y ) stands for ( x ≤ y ) ∧ ( x � = y ) 26 / 108
Bounded Fixpoint: The bounded iteration fix ( f )( n ) is defined by: fix ( f )( 0 )( s ) = ⊥ , s let v , s ′ = fix ( f )( n − 1 )( s ) in f v s fix ( f )( n )( s ) = 27 / 108
Semantics Let V 1 , ..., V n be sets of values (e.g., integers, booleans). We define the set of instantaneous values V as the smallest set which verifies the fix-point equation: V = V 1 + ... + V n + ( V × V ) A environment ρ is a function from a set of names to a set of values. Values are either elements of V , combinatorial functions ( V �→ V ) or stateful functions ( SFun ( V , V , S ) ): V + ( V �→ V ) + SFun ( V , V , S ) If ρ ( f ) = CoP ( p , s ) , we write ρ ( f ) I = p and ρ ( f ) S = s . 28 / 108
The semantics of an expression e is: ] State ] Init [ [ e ] ] ρ = CoF ( f , s ) where f = [ [ e ] and s = [ [ e ] ρ ρ We use two auxiliary functions. If e is an expression and ρ an environment which associates a value to a variable name: • [ ] Init [ e ] is the initial state of the transition function associated to e ; ρ • [ ] State [ e ] is the step function. ρ 29 / 108
] Init ] Init [ [ pre c ( e )] = ( c , [ [ e ] ρ ) ρ ] State ] State [ [ pre c ( e )] = λ ( m , s ) . m , [ [ e ] ( s ) ρ ρ ] Init ] Init [ [ f e ] = [ [ e ] ρ ρ ] State ] State [ [ f e ] = λ s . let v , s = [ [ e ] ( s ) in f ( v ) , s ρ ρ ] Init [ [ x ] = () ρ ] State [ [ x ] = λ s . ( ρ ( x ) , s ) ρ ] Init [ [ c ] = () ρ ] State [ [ c ] = λ s . ( c , s ) ρ ] Init ] Init ] Init [ [( e 1 , e 2 )] = ([ [ e 1 ] ρ , [ [ e 2 ] ρ ) ρ ] State ] State [ [( e 1 , e 2 )] = λ ( s 1 , s 2 ) . let v 1 , s 1 = [ [ e 1 ] ( s 1 ) in ρ ρ ] State let v 2 , s 2 = [ [ e 2 ] ( s 2 ) in ρ ( v 1 , v 2 ) , ( s 1 , s 2 ) 30 / 108
] Init ] Init [ [ run f e ] = ρ ( f ) I , [ [ e ] ρ ρ ] State ] State [ [ run f e ] = λ ( m , s ) . let v , s = [ [ e ] ( s ) in ρ ρ let r , m ′ = ρ ( f ) S m v in r , ( m ′ , s ) ] Init [ [ let node f x = e ] = ρ + [ CoP ( p , s ) / f ] ρ ] Init such that s = [ [ e ] ρ ] State and p = λ s , v . [ [ e ] ρ +[ v / x ] ( s ) 31 / 108
Fixpoint [ let rec x = e in e ′ ] ] Init ] Init [ e ′ ] ] Init [ = [ [ e ] ρ , [ ρ ρ [ let rec x = e in e ′ ] ] State λ ( s , s ′ ) . let v , s = fix ( λ s , v . [ ] State [ = [ e ] ρ +[ v / x ] ( s )) in ρ let v ′ , s ′ = [ [ e ′ ] ] State ρ +[ v / x ] ( s ′ ) in v ′ , ( s , s ′ ) Using a recursion on value, it corresponds to: [ let rec x = e in e ′ ] ] State λ ( s , s ′ ) . let rec v , ns = [ ] State [ = [ e ] ρ +[ v / x ] ( s ) in ρ let v ′ , s ′ = [ [ e ′ ] ] State ρ +[ v / x ] ( s ′ ) in v ′ , ( ns , s ′ ) Note that v is recursively defined 32 / 108
Control structure Note the difference between the two: the conditional “if/then/else” always executes its three arguments but not the “present”: ] Init ] Init ] Init ] Init [ [ if e then e 1 else e 2 ] = ([ [ e ] ρ , [ [ e 1 ] ρ , [ [ e 2 ] ρ ) ρ ] State ] State [ [ if e then e 1 else e 2 ] = λ ( s , s 1 , s 2 ) . let v , s = [ [ e ] ( s ) in ρ ρ ] State let v 1 , s 1 = [ [ e 1 ] ( s 1 ) in ρ ] State let v 2 , s 2 = [ [ e 2 ] ( s 2 ) in ρ ( if v then v 1 else v 2 , ( s , s 1 , s 2 )) ] Init ] Init ] Init ] Init [ [ present e do e 1 else e 2 ] = ([ [ e ] ρ , [ [ e 1 ] ρ , [ [ e 2 ] ρ ) ρ ] State [ [ present e do e 1 else e 2 ] = λ ( s , s 1 , s 2 ) . ρ ] State let v , s = [ [ e ] ( s ) in ρ if v ] State thenlet v 1 , s 1 = [ [ e 1 ] ( s 1 ) in ρ v 1 , ( s , s 1 , s 2 ) ] State else let v 2 , s 2 = [ [ e 2 ] ( s 2 ) in ρ v 2 , ( s , s 1 , s 2 ) 33 / 108
Modular Reset Reset a computation when a boolean condition is true. ] Init ] Init ] Init ] Init [ [ reset e 1 every e 2 ] = ([ [ e 1 ] ρ , [ [ e 1 ] ρ , [ [ e 2 ] ρ ) ρ ] State [ [ reset e 1 every e 2 ] = λ ( s i , s 1 , s 2 ) . ρ ] State let v 2 , s 2 = [ [ e 2 ] ( s 2 ) in ρ ] State let v 1 , s 1 = [ [ e 1 ] ( if v 2 then s i else s 1 ) in ρ v 1 , ( s i , s 1 , s 2 ) This definition duplicates the initial state. An alternative is: ] Init ] Init ] Init [ [ reset e 1 every e 2 ] = ([ [ e 1 ] ρ , [ [ e 2 ] ρ ) ρ ] State [ [ reset e 1 every e 2 ] = λ ( s 1 , s 2 ) . ρ ] State let v 2 , s 2 = [ [ e 2 ] ( s 2 ) in ρ ] Init let s 1 = if v 2 then [ [ e 1 ] else s 1 in ρ ] State let v 1 , s 1 = [ [ e 1 ] ( s 1 ) in ρ v 1 , ( s 1 , s 2 ) 34 / 108
Fix-point for mutually recursive streams Consider: let node sincos(x) = (sin, cos) where rec sin = int(0.0, cos) and cos = int(1.0, -. sin) The fix-point construction used in the kernel language is able to deal with mutually recursive definitions, encoding them as: sincos = (int(0.0, snd sincos), int(1.0, -. fst sincos) 35 / 108
Encoding mutually recursive streams A set of mutually recursive streams : e ::= let rec x = e and ... and x = e in e is interpreted as the definition of a single recursive definition such that: let rec x 1 = e 1 and ... and x n = e n in e means: let rec x = ( e 1 , ( e 2 , ( ..., e n )))[ e ′ 1 / x 1 , ..., e ′ n / x n ] in with: e ′ = fst ( x ) 1 e ′ = fst ( snd ( x )) 2 ... e ′ snd n − 1 ( x ) = n That is, if the n variables x 1 , ..., x n are streams whose outputs are of type CoStream ( T i , S i ) with i ∈ [ 1 .. n ] , fix ( . ) is applied to a function of type S → T 1 × ... × T n → ( T 1 × ... × T n ) × S with S = ( S 1 × ( ... × S n )) . All streams progress synchronously. 36 / 108
Where are the bottom values? From the semantics we have given, some equations have the constant bottom stream as minimal fix-point. E.g.: let node f(x) = o where rec o = o Indeed: ] State ρ +[ v / o ] ( s )) = fix ( λ s , v . ( v , s )) = λ s , v . ( ⊥ , s ) fix ( λ s , v . [ [ o ] An other example is: let node f(z) = (x, y) where rec x = y and y = x Indeed: ] State fix ( λ s , v . [ [( snd ( v ) , fst ( v ))] ρ +[ v / x ] ( s )) = fix ( λ s , v . ( snd ( v ) , fst ( v )) , s ) = λ s . ( ⊥ , ⊥ ) , s We are interesting in finding sufficient conditions to ensure that the output is not bottom or, even more, that it does not contain bottom. 37 / 108
Def-use chains In term of def-use chains of variables based on the occurrence of variables in expression, there is a cyclic dependence in both examples: x depends on y which depends on x The following definition does not define a bottom stream (provided that inputs are non bottom streams). 5 let node euler_forward(h, x0, xprime) = x where rec x = x0 fby (x +. h *. xprime) 5 We suppose that all imported functions are total. 38 / 108
Break the dependence cycles with a unit delay The graphical argument we used — the dependence graph between variables is cyclic or not — can be adapted to take the unit delay into account. Say that pre c ( e ) does not depend on variable in e ; hence, a variable x defined by an equation x = e only depends on the variables in e which do not appear on the right of a unit delay. The dependence relation between variables in the euler_backward is acyclic. 39 / 108
Is that enough? The dependence graph is a rough abstraction of the dependence relation. It does not take into account the actual values of expressions. It over approximate instantaneous dependences. Some sets of equations whose associate dependence graph is cyclic do define non bottom streams. E.g.,: let node f(y) = x where rec x = if false then x else 0 The conditional only needs the current value of its first (or second) argument with the condition is true (or false). This program is rejected by Lustre compilers. 40 / 108
Mutually recursive definitions The notion of dependence is subtle. All function below are such that if x is non bottom, outputs z and t are non bottom. let node good1(x) = (z, t) where rec z = t and t = 0 fby z let node good2(x) = (z, t) where rec (z, t) = (t, 0 fby z) let node good3(x) = (fst r, snd r) where rec r = (snd r, 0 fby (fst r)) let node pair(r) = (snd r, 0 fby (fst r)) let node good4(x) = r where rec r = pair(r) Do we want to accept all of them? What is the criterium to accept them or not? The next lesson will be devoted to the precise definition of what is a dependence and its exploitation to generate sequential code. 41 / 108
The following is a classical example that is “constructively causal” but is also rejected by Lustre compilers. let node mux(c, x, y) = present c then x else y let node constructive(c, x) = y where rec rec x1 = mux(c, x, y2) and x2 = mux(c, y1, x) and y1 = f(x1) and y2 = g(x2) and y = mux(c, y2, y1) If we look at the def-use chains of variables, there is a cycle in the dependence graph: • x1 depends on c , x and y2 ; • x2 depends on c , y1 and x ; • y1 depends on x1 ; y2 depends on x2 ; • y depends on c , y2 and y1 . By transitivity, y2 depends on y2 and y1 depends on y1 . 42 / 108
Yet, if c and x are non bottom streams, the fix-point that defines (x1,x2,y1,y2,y) is a non bottom stream. It can be proved to be equivalent to: let node constructive(c, x) = y where rec rec y = mux(c, g(f(x)), f(g(x))) Question: can you prove it? How? In term of an implementation into a circuit, the cyclic version has a single occurrence of f and g whereas the second has two copies of each. A cyclic combinatorial circuit can be exponentially smaller than its non cyclic counterpart. 6 The causality analysis ensures that an expression does not produce bottom and can be translated into an expression with no fix-point. 6 See notes for references. 43 / 108
The following example (written in Zelus) also defines a node whose output is non bottom: let node composition(c1, c2, y) = (x, z, t, r) where rec present c1 then do x = y + 1 and z = t + 1 done else do x = 1 and z = 2 done and present c2 then do t = x + 1 and r = z + 2 done else do t = 1 and r = 2 done that can be interpreted as the following program in the language kernel: let node composition(c1, c2, y) = (x, z, t, r) where rec (x, z) = present c1 then (y + 1, t + 1) else (1, 2) and (t, r) = present c2 then (x + 1, z + 2) else (1, 2) 44 / 108
Is it causal? Supposing the c1 , c2 and y are not bottom values, taking e.g., true for c1 and c2 , starting with x 0 = ⊥ , z 0 = ⊥ , t 0 = ⊥ and r 0 = ⊥ , the fixpoint is the limit of the sequence: x n = y + 1 ∧ z n = t n − 1 + 1 ∧ t n = x n − 1 + 1 ∧ r n = z n − 1 + 2 and is obtained after 4 iterations. This program is causal: if inputs are non bottom values, all outputs are non bottom values and this is the case for all computations of it. 45 / 108
The inpact of static code generation Nonetheless, if we want to generate statically scheduled sequential code, the control structure must be duplicated: (1) test c1 to compute x ; (2) test c2 to compute t ; (3) test (again) c1 to compute z ; (4) test (again) c2 to compute r let node composition(c1, c2, y) = (x, z, t, r) where rec present c1 then do x = y + 1 done else do x = 1 done and present c2 then do t = x + 1 done else do t = 1 done and present c1 then do z = t + 1 done else do z = 2 done and present c2 then do r = z + 2 done else do r = 2 done Accepting program with interwined dependences has an impact on code size and efficiency. It is possible to overconstraint the causality analysis and control structures to be atomic (outputs all depend on all inputs). 46 / 108
Removing Recursion Yet, the semantics we have given computes a step function which must be evaluated lazilly. Is this really a progress w.r.t the co-inductive semantics? Some recursive equations can be translated into non recursive definitions. Consider the stream equation: let rec nat = 0 fby (nat + 1) in nat Can we get rid of recursion in this definition? Surely we can, since it can be compiled into a finite state machine corresponding to the co-iterative process: nat = Co ( λ s . ( s , s + 1 ) , 0 ) 47 / 108
First: let us unfold the semantics Consider the recursive equation: rec nat = (0 fby nat) + 1 Let us try to compute the solution of this equation manually by unfolding the definition of the semantics. Let x = CoF ( f , s ) where f is a transition function of type f : S → X × S and s : S the initial state, we write: x . step for f and x . init for x : init for s . The bottom stream, to start with, is: x 0 = CoF ( λ s . ( ⊥ , s ) , ⊥ ) 48 / 108
The equation that defines nat can be rewritten as let rec nat = f ( nat ) in nat with let node f x = ( 0 fby x ) + 1. The semantics of f is: f = CoP ( f s , s 0 ) = CoP ( λ s , x . ( s + 1 , x ) , 0 ) Solving nat = f ( nat ) amount at finding a stream X such that: X ( s ) = let v , s ′ = X ( s ) in f s s v 49 / 108
Let us proceed iteratively by unfolding the definition of the semantics. We have: λ s . let v , s ′ = x 0 . step s in f s s v x 1 . step = = λ s . f s s ⊥ = λ s . s + 1 , ⊥ x 1 . init = 0 λ s . let v , s ′ = x 1 . step s in f s s v x 2 . step = = λ s . let v = s + 1 in f s s v = λ s . let v = s + 1 in s + 1 , v = λ s . s + 1 , s + 1 x 2 . init = 0 λ s . let v , s ′ = x 2 . step s in f s s v x 3 . step = = λ s . let v = s + 1 in f s s v = λ s . let v = s + 1 in s + 1 , v = λ s . s + 1 , s + 1 x 3 . init = 0 We have reached the fix-point CoF ( λ s . ( s + 1 , s + 1 ) , 0 ) in three steps. 50 / 108
Syntactically Guarded Stream Equations We give now a simple, syntactic condition under which the semantics of mutually recursive stream equations does not need any fix point. Consider a node f : CoStream ( T , S ) → CoStream ( T , S ′ ) whose semantics is ( f t , s t ) with f t : S ′ → T → T ′ × S ′ and s t : S ′ . The semantics of an equation y = f ( y ) is: 7 ] Init [ [ let rec y = f ( y ) in y ] = s t ρ λ s . let rec v , s ′ = f t v s in v , s ′ ] State [ [ let rec y = f ( y ) in y ] = ρ The recursion on time (a stream recursion) is transformed into a recursion on the instant. 7 We reason upto bisimulation, that is, independently on the actual representation of the internal state. 51 / 108
Two cases can happen: • We deal with a 0-order expression (a stream expression or product of 0-order expressions), then: • Either the first element of the pair f t v s , that is v , s ′ depends on v and we have an unbounded recursion — the program contains a causality loop —; • or it does not and the evaluation succeeds. • the expression is an higher order one and its boundedness depends on semantic conditions to be checked in each case. For example, the following equation: let rec nat = nat + 1 in nat is not causal since x depends instantaneously on itself and its evaluation have an unbounded recursion. 52 / 108
When the program does not contain any causality loop, it means that indeed the recursive evaluation of the pair v , s ′ can be split into two non recursive ones. This case appears, for example, when every stream recursion appears on the right of a unit delay pre . A synchronous compiler takes advantage of this in order to produce non recursive code like the co-iterative nat expression given above. Yet, if we are interested in defining an interpreter only, the co-iterative semantics can be used for that purpose. 53 / 108
For example, consider the equation y = f ( v fby x ) . Its semantics is: ] Init [ [ let rec x = f ( v fby x ) in x ] = ( v , s t ) ρ let rec v , s ′ = f t m s in ] State [ [ let rec x = f ( v fby x ) in x ] ( m , s ) = ρ v , ( v , s ′ ) But this time, the recursion is no more necessary, that is: let v , s ′ = f t m s in v , ( v , s ′ ) ] State [ [ let rec x = f ( v fby x ) in x ] ( m , s ) = ρ 54 / 108
Putting Mutually Recursive Equation in Normal Form Consider: let rec sin = 0.0 fby (sin +. h *. cos) and cos = 1.0 -> (0.0 fby cos) +. h *. sin in sin, cos Rewrite it into: let rec sin = 0.0 fby sin_next and pre_cos = 0.0 fby cos and sin_next = sin +. h *. cos and cos = 1.0 -> pre_cos +. h *. sin sin, cos All the unit delay are un-nested; their argument is a variable. Gather equations on delays on the top; statically schedule other equations according to read/write variables. 55 / 108
The transition function is: λ ( m 1 , m 2 , m 3 ) . let sin = m 1 in let pre _ cos = m 2 in let sin _ next = sin + . h ∗ . cos in let cos = if m 3 then 1 . 0 else pre _ cos + . h ∗ . sin in ( sin , cos ) , ( sin _ next , cos , false ) and initial state: ( 0 . 0 , 0 . 0 , true ) There is no more recursion in the transition function. 56 / 108
The Semantics for Normalised Equations Consider a set of mutually recursive equations such that it can be put under the following form: let rec x 1 = v 1 fby nx 1 and ... x n = v n fby nx n and p 1 = e 1 and ... and p k = e k in e where ∀ i , j . ( i < j ) ⇒ Var ( e i ) ∩ Var ( p j ) = ∅ where Var ( p ) and Var ( e ) are the set of variable names appearing in p and e . 57 / 108
Its transition function is: ] State λ ( x 1 , ..., x n , s 1 , ..., s k , s ) . let p 1 , s 1 = [ [ e 1 ] ( s 1 ) in ρ let ... in ] State let p k , s k = [ [ e k ] ( s k ) in ρ ] State let r , s = [ [ e ] ( s ) in ρ r , ( nx 1 , ..., nx n , s 1 , ..., s k , s ) with initial state: ( v 1 , ..., v n , s 1 , ..., s k , s ) ] Init ] Init if [ [ e i ] = s i and [ [ e ] = s . ρ ρ When a set of mutually recursive streams can be put in the above form, its transition function does not need a fix-point. It can be statically scheduled into a function that can be evaluated eagerly. This removing of the recursion is the basis of generation of statically scheduled code done by a synchronous language compiler. Exercice: prove that the new semantics for the let/rec operation is correct, that is, it produces the same stream as the original semantics. 58 / 108
Extension of the language kernel: local variables, mutually recursive definitions, hierarchical automata 59 / 108
The language kernel we have considered is similar to Lustre. • It is first-order as Lustre but adds type polymorphism, a reset and an elementary control-structure ( present/then/else ) to execute a block conditionally. • All functions are length preserving: there is no when/merge or current operation. We consider now an extended language that incorporates the programming construct that exists in Scade 6. 60 / 108
Mutually Recursive Equations Equations are extended with local definitions: p = e | E and E E ::= | local v in E x | x init e | x default e v ::= p ::= x | ( p , ..., p ) Expressions are extended with a construct to access the last value of a stream: ::= ... | last x e 61 / 108
Environment The construct local x in E declares x to be local in E . The construct local x init e in E declares x to be local and the last computed value of x to be initialized with the value of e . The construct local x default e in E declares x to be local and the default value of x to be the value of e , at instants where no definition of x is given. 62 / 108
Conditionals over Equations If e is an expression whose type is a sum type t = C 1 | ... | C n , • match e with C i 1 → E 1 | ... | C i n → E n activates equation E j such that i j is the first index such that e = C i j , with 1 ≤ i 1 , ..., i n ≤ n . • present e do E 1 else E 2 activates equations E 1 or E 2 according to the boolean value of e . E ::= ... | match e with C → E ... C → E | present e do E 1 else E 2 63 / 108
Hierarchical Automata A automaton which describe a system with several modes and transitions between them. Such an automaton is characterized by: • A finite set of states. • In every state, a set of equations with variables that are possibly local to the state. • A set (possibly empty) of “weak transitions” (keyword until ) which define the active state for the next reaction. • A set (possibly empty) of “strong transitions” (keyword unless ) which define the active set of equations for the current reaction. • Transitions can be by “reset” or by “history”. Rmq: Contrary to Scade 6 and Lucid Synchrone, weak and strong transitions cannot be mixed inside an automaton. 64 / 108
The syntax is extended in the following way. E ::= ... | automaton S → u wt | ... | S → u wt | automaton S → u st | ... | S → u st local v in u | do E u ::= st ::= unless t | ǫ wt ::= until t | ǫ ::= e then S et | e continue S et t else t et | ǫ et ::= 65 / 108
Examples in Zelus type t = Incr | Decr | Idle let f(c) = local o init 0 do match c with | Idle -> (* o keeps its previous value, i.e., o = last o *) do done | Incr -> do o = last o + 1 done | Decr -> do o = last o - 1 done in o 66 / 108
Examples in Zelus let node controller(auto, error, input) = output where rec automaton | Manual -> do output = input unless auto then Auto | Auto -> do output = run pid(p, i, d, error) unless (not auto) then Manual let node await(a) = go where rec automaton | Await -> do go = false unless a then Run | Go -> do go = true done let node abro(a, b, r) = go where rec reset automaton | Await -> do go = false unless (run await(a) && run await(b)) then Go | Go -> do go = true done every r 67 / 108
Typing constraints 68 / 108
k • G | H ⊢ E : H defines the typing of an equation E in an environment H , with result kind k and producing an environment H ’. k ⊢ hw : H ′ defines the typing of the body of a state hw of an • G | H | L automaton with weak preemption. L is the set of states of the automaton; H is the typing environment; k is the kind; hw is the body associated to state S ; H ′ is the returned environment. k ⊢ hs : H ′ applies for the body of a strong • A similar predicate G | H | L automaton. k k • G | H | L ⊢ t and G | H | L ⊢ ts define the typing of a transition. To make the notation ligther, we omit the global environment in the rules. 69 / 108
Operations on typing environments If H 1 and H 2 are two environment, H 1 ∗ H 2 join the two. H 1 ∗ ... ∗ H n = ( ... ( H 1 ∗ H 2 ) ∗ ... ) ∗ H n . ( H 1 ∗ H 2 )( x ) = H 1 ( x ) if H 1 ( x ) = H 2 ( x ) ( H 1 ∗ H 2 )( x ) = H 1 ( x ) if x ∈ Dom ( H 1 ) ∧ x �∈ Dom ( H 2 ) ( H 1 ∗ H 2 )( x ) = H 2 ( x ) if x ∈ Dom ( H 2 ) ∧ x �∈ Dom ( H 1 ) H 1 + H 2 is the union of the two and is defined only when their two domains do not intersect. If H is a typing environment such that H = H ′ + [ x : t ] , then H \ x = H ′ . 70 / 108
(A-weak) k k ∀ i , j ∈ [ 1 .. n ] H ⊢ u i : H i H , H i | { S 1 , ..., S n } ⊢ wt i ( i � = j ) ⇒ ( S i � = S j ) 1 ⊢ automaton ( S i → u i wt i ) i ∈ [ 1 .. n ] : H 1 ∗ ... ∗ H n H (A-strong) k k ∀ i , j ∈ [ 1 .. n ] H ⊢ u i : H i H | { S 1 , ..., S n } ⊢ st i ( i � = j ) ⇒ ( S i � = S j ) 1 ⊢ automaton ( S i → u i st i ) i ∈ [ 1 .. n ] : H 1 ∗ ... ∗ H n H (Eq) k k H ⊢ p : t H ⊢ e : t H ( x i ) = t i Vars ( p ) = { x 1 , ..., x n } k ⊢ p = e : [ t 1 / x 1 , ..., t n / x n ] H 71 / 108
(Local) (And) k k k k ⊢ E : H ′ ⊢ v : H 0 ⊢ E 1 : H 1 ⊢ E 2 : H 2 H H , H 0 H H k k ⊢ local v in E : H ′ \ x H H ⊢ E 1 and E 2 : H 1 + H 2 (Last) 1 H , x : t ⊢ last x : t (Match) k k H ⊢ e : t t = C 1 | ... | C n ∀ i ∈ [ 1 .. n ] H ⊢ E i : H i k ⊢ match e with ( C i → E i ) i ∈ [ 1 .. n ] : H 1 ∗ ... ∗ H n H 72 / 108
(Local-u) (Do-u) (Until) k k k k ⊢ u : H ′ ⊢ E : H ′ H ⊢ v : H 0 H , H 0 H H | L ⊢ t k k k ⊢ local v in u : H ′ \ x ⊢ do E : H ′ H | L ⊢ until t H H (Unless) (Else) (Epsilon) k k k H | L ⊢ t H | L ⊢ t H | L ⊢ et k H | L ⊢ ǫ k k H | L ⊢ unless t H | L ⊢ else t et 73 / 108
(ResetTransition) (ContinueTransition) (varpat) k k H ⊢ e : bool S ∈ L H ⊢ e : bool S ∈ L k ⊢ x : [ t / x ] H k k H | L ⊢ e then S H | L ⊢ e continue S (init) (default) k k H ⊢ e : t H ⊢ e : t 1 k H ⊢ x init e : [ t / x ] ⊢ x default e : [ t / x ] H 74 / 108
Semantics 75 / 108
Environment The environement is complemented to possibly associate a default or initial value to a variable. ρ ::= ρ + [ v / x ] | ρ + [ v / default x ] | [ v / last x ] | [] If ρ and ρ ′ are two environments, we write ρ by ρ ′ the completion of ρ with default or initial values from ρ ′ . This operation is used to define the value of a variable in ρ by [] = ρ ρ by ( ρ ′ + [ v / default x ]) ( ρ + [ v / x ]) by ρ ′ = ρ by ( ρ ′ + [ v / last x ]) ( ρ + [ v / x ]) by ρ ′ = ρ by ( ρ ′ + [ v / x ]) ρ by ρ ′ = If p is a pattern and v is a value, match v with p builds the environment by matching v by p such that: [ v | x ] = [ v / x ] [( v 1 , v 2 ) | ( p 1 , p 2 )] = [ v 1 | p 1 ] + [ v 2 | p 2 ] 76 / 108
] Init If E is an equation, ρ is an environment, [ [ E ] is the initial state and ρ ] State [ [ E ] is the step function. The semantics of an equation E is: ρ ] Init ] State [ [ E ] ] ρ = [ [ E ] ρ , [ [ E ] ρ ] Init ] Init [ [ p = e ] = [ [ e ] ρ ρ ] State ] State [ [ p = e ] = λ s . let v , s = [ [ e ] ( s ) in [ v | p ] , s ρ ρ ] Init ] Init ] Init [ [ E 1 and E 2 ] = ([ [ E 1 ] ρ , [ [ E 2 ] ρ ) ρ ] State ] State [ [ E 1 and E 2 ] = λ ( s 1 , s 2 ) . let ρ 1 , s 1 = [ [ E 1 ] ( s 1 ) in ρ ρ ] State let ρ 2 , s 2 = [ [ E 2 ] ( s 2 ) in ρ ρ 1 + ρ 2 , ( s 1 , s 2 ) 77 / 108
Notation: If ρ = ρ ′ + [ v / x ] , ρ \ x = ρ ′ . ] Init ] Init [ [ local x in E ] = [ [ E ] ρ ρ ] State let ρ ′ , s = fix ( λ s , ρ ′ . [ ] State [ [ local x in E ] ( s ) = [ E ] ρ + ρ ′ ( s ))( s ) in ρ ρ ′ \ x , s ] Init ] Init [ [ local x default v in E ] = [ [ E ] ρ ρ ] Init ] Init [ [ local x init v in E ] = ( v , [ [ E ] ρ ) ρ ] State [ [ local x default v in E ] ( s ) = ρ let ρ ′ , s = fix ( λρ ′ , s . [ ] State [ E ] ρ + ρ ′ +[ v / default x ] ( s )) in ρ ′ \ x , s ] State [ [ local x init v in E ] ( w , s ) = ρ ] State let ρ ′ , s = fix ( λρ ′ , s . [ [ E ] ρ + ρ ′ +[ w / last x ] ( s )) in ρ ′ \ x , ( ρ ′ ( x ) , s ) 78 / 108
Semantics for conditionals The semantics for a conditional must consider the case where a branch defines a value for a variable x in one branch but not the other branch. We take the following convention: • If a variable x is declared with a default value v , then a missing equation for x in a branch means that x = v in that branch. • Otherwise, x = last x , that is, x keeps its previous value. • If x is declared with an initial value for last x , this means that x has a definition in every branch. Otherwise, there is a potential initialisation issue which has to be checked by other means. 79 / 108
Semantics for Conditionals ] Init ] Init ] Init ] Init [ [ present e do E 1 else E 2 ] = ([ [ e ] ρ , [ [ E 1 ] ρ , [ [ E 2 ] ρ ) ρ ] State [ [ present e do E 1 else E 2 ] ( s , s 1 , s 2 ) = ρ ] State let v , s = [ [ e ] ( s ) in ρ if v ] State thenlet ρ 1 , s 1 = [ [ E 1 ] ( s 1 ) in ρ ρ 1 by ρ [ N \ N 1 ] , ( s , s 1 , s 2 ) ] State else let ρ 2 , s 2 = [ [ E 2 ] ( s 2 ) in ρ ρ 2 by ρ [ N \ N 2 ] , ( s , s 1 , s 2 ) where N = N 1 ∪ N 2 and N 1 = Def ( E 1 ) and N 2 = Def ( E 2 ) ] Init ] Init ] Init ] Init [ [ match e with ( C i → E i ) i ∈ [ 1 .. n ] ] = ([ [ e ] ρ , [ [ E 1 ] ρ , ..., [ [ E n ] ρ ) ρ 80 / 108
The Transition Function: ] State [ [ match e with ( C i → E i ) i ∈ [ 1 .. n ] ] ( s , s 1 , ..., s n ) = ρ ] State let v , s = [ [ e ] ( s ) in match v with ρ � C i → let ρ i , s i = [ ] State � [ E i ] ( s i ) in ρ ρ i by ρ [ N \ N i ] , ( s , s 1 , ..., s n ) i ∈ [ 1 .. n ] where N = ∪ i ∈ [ 1 .. n ] ( N i ) and N i = Def ( E i ) The Last Computed Value: ] Init [ [ last x ] = () ρ ] State [ [ last x ] = λ s .ρ ( last x ) , s ρ 81 / 108
Initial state of the transition function ] Init [ [ ǫ ] = () ρ ] Init ] Init ] Init [ [ e then S et ] = ([ [ e ] ρ , [ [ et ] ρ ) ρ ] Init ] Init ] Init [ [ e continue S et ] = ([ [ e ] ρ , [ [ et ] ρ ) ρ ] Init ] Init ] Init [ [ else t et ] = ([ [ t ] ρ , [ [ et ] ρ ) ρ ] Init ] Init [ [ unless t ] = [ [ t ] ρ ρ ] Init ] Init [ [ until t ] = [ [ t ] ρ ρ ] Init [ [ automaton ( S i → u i wt i ) i ∈ [ 1 .. n ] ] = ρ ] Init let ( s i = [ [ u i ] ρ ) i ∈ [ 1 .. n ] in ] Init let ( s ′ i = [ [ wt i ] ρ ) i ∈ [ 1 .. n ] in ( S 0 , false , ( s 1 , ..., s n ) , ( s ′ 1 , ..., s ′ n )) ] Init [ [ automaton ( S i → u i st i ) i ∈ [ 1 .. n ] ] = ρ ] Init let ( s i = [ [ u i ] ρ ) i ∈ [ 1 .. n ] in let ( s ′ ] Init i = [ [ st i ] ρ ) i ∈ [ 1 .. n ] in ( S 0 , false , ( s 1 , ..., s n ) , ( s ′ 1 , ..., s ′ n )) 82 / 108
Transition functions Given a transition t , a name ps of a state in the automaton, a value pr for ] ps , pr ( s ′ ) returns a new state name, a new reset the reset condition, [ [ t ] ρ condition and a new state. ] s , r ρ ( s ′ ) [ [ ǫ ] = s , r ] s , r ] Init [ [ e then S st ] ρ ( s 1 , s 2 ) = let s 1 = if r then [ [ e ] else s 1 in ρ ] State let v , s 1 = [ [ e ] ( s 1 ) in ρ ] s , r let ( s , r ) , s 2 = [ [ st ] ρ ( s 2 ) in if v then ( S , true ) , ( s 1 , s 2 ) else ( s , r ) , ( s 1 , s 2 ) ] s , r ] Init [ [ e continue S st ] ρ ( s 1 , s 2 ) = let s 1 = if r then [ [ e ] else s 1 in ρ ] State let v , s 1 = [ [ e ] ( s 1 ) in ρ ] s , r let ( s , r ) , s 2 = [ [ st ] ρ ( s 2 ) in if v then ( S , false ) , ( s 1 , s 2 ) else ( s , r ) , ( s 1 , s 2 ) 83 / 108
Automata ] State ( ps , pr , s , s ′ ) = [ [ automaton ( S i → u i wt i ) i ∈ [ 1 .. n ] ] ρ ] ps , pr let ( ρ, ns , nr ) , ( s , s ′ ) = [ ( s , s ′ ) in [( S i → u i wt i ) i ∈ [ 1 .. n ] ] ρ ρ, ( ns , nr , s , s ′ ) ] State ( ps , pr , s , s ′ ) = [ [ automaton ( S i → u i st i ) i ∈ [ 1 .. n ] ] ρ ] ps , pr let ( ρ, ns , nr ) , ( s , s ′ ) = [ ( s , s ′ ) in [( S i → u i st i ) i ∈ [ 1 .. n ] ] ρ ρ, ( ns , nr , s , s ′ ) 84 / 108
] pr • [ [ u ] ρ ( s ) resets u when pr is true, that is: ] pr ] State ] Init [ [ u ] ρ ( s ) = [ [ u ] ( if ps then [ [ u ] else s ) ρ ρ ] ps , pr (( s 1 , ..., s n ) , ( s ′ 1 , ..., s ′ [( S i → u i wt i ) i ∈ [ 1 .. n ] ] [ n )) = ρ match s with ] pr S i → let ρ, s i = [ [ u i ] ρ ( s i ) in ] ps , pr let ( ns , nr ) , s ′ ( s ′ i = [ [ wt i ] i ) in ρ ρ, ( ns , nr , ( s 1 , ..., s n ) , ( s ′ 1 , ..., s ′ n )) i ∈ [ 1 .. n ] ] ps , pr (( s 1 , ..., s n ) , ( s ′ 1 , ..., s ′ [( S i → u i st i ) i ∈ [ 1 .. n ] ] [ n )) = ρ let ( cs , cr ) , ( s ′ 1 , ..., s ′ n ) = match s with ] ps , pr � S i → let ( cs , cr ) , s ′ ( s ′ � i = [ [ st i ] i ) in ρ ( cs , cr , ( s ′ 1 , ..., s ′ n )) i ∈ [ 1 .. n ] in match cs with ] cr � S i → let ρ, s i = [ � [ u i ] ρ ( s i ) in ρ, ( cs , cr , ( s 1 , ..., s n ) , ( s ′ 1 , ..., s ′ n )) i ∈ [ 1 .. n ] 85 / 108
Interpretation • The transition function associated with the automaton construct is executed in an initial state. • This state if of the form ( ps , pr , s , s ′ ) . ps is the current state of the automaton. It is initialised with the initial state of the automaton. pr is the reset status. It is initialized with the value false. s is the state to execute the code of the strong transitions; s ′ is the state to execute the body of the automaton; s ′ is the state to execute the transitions. • For an automaton with weak transition, the body is executed, then the transitions. • For an automaton with strong transitions, the code of transitions of the current state are executed. This determines the active state. Then, the corresponding body is executed. 86 / 108
Exercices and questions • Defines the semantics of e 1 fby e 2 . • Based on the previous definitions, write an interpretor in Haskell or OCaml. • Defines a small langage to represent the transition functions. Rewrite your interpretor so that it produces a term from this language. 87 / 108
Non length-preserving stream functions 88 / 108
Non length-preserving stream functions The stream functions we have considered are length preserving : to produce one output, their step function needs only one input. This is what allowed us to implement a stream function with type: CStream ( T , S ) → CStream ( T ′ , S ′ ) by a value of type: ( S ′ → T → T ′ × S ′ ) × S ′ Hence, it is not possible to represent non length preserving functions like the function even which removes one element over two of the input stream. In Haskell, with : the operation on lists: 8 even (x : (x’ : xs)) = x : (even xs) The destructor function of the input hd,tl has to be applied twice in the transition function of the result. 8 See notes of the previous class. 89 / 108
This would also be the case of filter-like functions like when defined as: ( x : xs ) when ( true : cs ) = x : xs when cs ( x : xs ) when ( false : cs ) = xs when cs 90 / 108
Complementing Streams with Absent Values An obvious idea to overcome the problem and turn these functions into synchronous ones would be to consider the functor F : F T ( S ) = S + ( T × S ) with the two value constructors (injective functions): S : S → F T S and P : T × S → F T S where P stands for “present” and S for “silent”. The set of streams is now: CLStream ( T , S ) = ( S → ( S + ( T × S )) × S Given t , s : CLStream ( T , S ) , the process ( t s ) can be silent, that is, it only updates its state without outputing values and return the next state or output a value and returns the next state. 91 / 108
Then a transition function for even could be: even ( CoF ( t , s )) = CoF λ ( e , s ) . match t s with | S ( sx ) → S ( e , sx ) | P ( vx , sx ) → if e then P ( vx , ( false , sx )) else S ( true , sx ) ( true , sx ) where e is a boolean state condition telling whether the current step is an even one or not. However, the question is now: does this functor still define streams? An answer to this question is as follows: 92 / 108
The co-algebra of clocked streams Theorem (Co-algebra of clocked streams) The terminal co-algebra associated to the functor F T ( S ) = S + ( T × S ) • as ground set the set of streams of values in Value ( T ) = 1 + T , the set T complemented with an empty value with 1 = { () } with the value constructors: E : Value ( T ) and V : T → Value ( T ) : Stream ( T ) = ( Value ( T )) N • and as destructor, dest ( v : vs ) = match v with E → S ( vs ) | V ( v ′ ) → P ( v ′ , vs ) 93 / 108
Proof: Given tx : S → F T S a transition function, let us denote by next ( ., . ) the iterated next state function next ( . ) : match tx s with S ( s ′ ) → s ′ | P ( v , s ′ ) → s ′ next ( s ) = next ( n , s ) = if n = 0 then next ( s ) else next ( n − 1 , next ( s )) Any function run which makes the following diagram commute: ( Value ( T )) N − − − run − → S | | dest tx ↓ ↓ F T ( Value ( T )) N − − − F id run − → F T S 94 / 108
yields: dest ( run ( s )) = match run ( s )( 0 ) with | E → S ( λ n . run ( s )( n + 1 )) | V ( v ) → P ( v , λ n . ( run ( s )( n + 1 ))) = match t s with | S ( s ′ ) → S ( run ( s ′ )) | P ( v , s ′ ) → P ( v , run ( s ′ )) that is: match t s with S ( s ′ ) → E | P ( v , s ′ ) → V ( v ) run ( s )( 0 ) = run ( s )( n + 1 ) = match t s with | S ( s ′ ) → run ( s ′ )( n ) | P ( v , s ′ ) → run ( s ′ )( n ) = run ( next ( s )) n This uniquely defines run as: run ( s )( n ) = match t ( next ( n , s )) with S ( s ′ ) → E | P ( v , s ′ ) → V ( v ) 95 / 108
Definition (Clocks) The clock of a clocked stream s : ( 1 + T ) N is the boolean stream: clock ( s ) = λ n . match s ( n ) with | E → false | V ( v ) → true Note that clocks are just ordinary streams, i.e. without E elements. Yet this result shows also that we can as well assimilate clocked streams with ordinary streams with “empty” values 9 . This allows us to easily reuse the result for length preserving streams developed previously. We thus will adopt this point of view in the sequel, by taking: Value ( T ) = E + V ( T ) CLStream ( T , S ) = CStream ( Value ( T )) S 9 This quite obvious result has been used and rediscovered many times since the pioneering work of F. Boussinot [1]. Yet, the above proof may bring some insight about the need for “empty” values. 96 / 108
New Definitions for Primitives We can now revisit our previously defined operators as well as create new ones. When defining binary operators, like extend we now find the following problem: what to do if one argument yields a value while the other one does not? At least three possibilities are open: 1) store the value in a state variable implementing a FIFO queue, until it matches an incoming value of the other argument, 2) generate an execution error, 3) or statically reject this situation. As an extension of what is done for Lustre [5] we choose the third solution and write: ( CoF ( tf , if )) ( CoF ( te , ie )) = CoF ( λ ( sf , se ) . match ( tf sf ) , ( te se ) with | ( E , sf ′ ) , ( E , se ′ ) → E , ( sf ′ , se ′ ) vf ) , sf ′ ) , ( V ( ve ) , se ′ ) → V ( vf ve ) , ( sf ′ , se ′ ) , | ( V ( ( if , ie )) 97 / 108
Under the condition that the clocks of the two arguments are the same. Otherwise, the program should raise an execution error (a pattern-matching failure). The purpose of the clock calculus is to statically ensure that such errors do not occur. When expressions have passed the analysis, clock information is used to remove the dynamic test of presence/absence. 98 / 108
Primitive Functions When: The co-iterative definition for the filter is as follows, assuming its two arguments share the same clock: ( CoF ( tx , ix )) when ( CoF ( tc , ic )) = CoF ( λ ( sx , sc ) . match ( tx sx ) , ( tc sc ) with | ( E , sx ′ ) , ( E , sc ′ ) → E , ( sx ′ , sc ′ ) | ( V ( vx ) , sx ′ ) , ( V ( true ) , sc ′ ) → V ( vx ) , ( sx ′ , sc ′ ) | ( V ( vx ) , sx ′ ) , ( V ( false ) , sc ′ ) → E , ( sx ′ , sc ′ ) , ( ix , ic )) The clock of the result depends on the boolean condition. 99 / 108
If the clock of the two arguments is ( CoF ( tcl , scl )) , the clock of the result is CoF ( tcl , scl ) on CoF ( tc , sc ) : CoF ( tcl , icl ) on CoF ( tc , ic ) = CoF λ ( scl , sc ) . match tcl scl with | false , scl ′ → let E , sc ′ = tc sc in false , ( scl ′ , sc ′ ) | true , scl ′ → let V ( vc ) , sc ′ = tc sc in vc , ( scl ′ , sc ′ ) ( icl , ic )) Note that, according to the definition, a clock is an ordinary stream which has no “silent” move. 100 / 108
Recommend
More recommend