Let’s step back and think about what a discrete state system looks like.
- We have a set E of discrete “events” called the event alphabet, The event alphabet defines all the possible events and inputs that can cause state to change.
- A finite sequence of events over E then can be thought of as a system history, describing what series of events drove the system from its initial state (which is the point at which no events at all had happened.) You could complain at this point that deterministic, single thread of execution (the sequence) models can’t describe non-determinism, parallelism, concurrency, and abstract properties, but you’d be wrong – keep reading.
- Now we are going to take an odd step – I don’t want to start with a state machine or other object that represents the whole system. I want to start with state variables that express some system property and how it changes in response to events. For example, a state variable Executing might tell us the process identifier of the currently running (executing) process or it could be set valued in a multi-processor environment. And priorityp might be the priority of the process with identifier, p. As the system advances state, the values of the state variable change but often we want to have properties that are true in any state. For example, (Executing=p AND priorityq > priorityp implies TimeSinceSwitch < t) would be a good property for some operating system that needs to schedule by priority but also let lower priority processes advance.
- These state functions can be thought of as dependent variables that depend on the event sequence and some map that extracts information from the event sequence. The event sequence is the free variable and the state variable is a dependent variable that embodies some aspect of system state. Everything that determines current state is in the event sequence but state is not a thing, not an object, it’s the cumulative information of multiple state variables depending on that sequence.
- We might have many state variables to specify how an operating system works or even how a simple gate works. Let’s think of all of them at the same level of abstraction as depending on the same event sequence (finite, of course).
- The key to this approach is to be able to compactly define state variables and abstract properties of state variables even though the event alphabets and maps on sequences of events will often be enormously complicated.
- More precisely, say a variable y is a state variable for an event alphabet E if y=f(σ) for some function f where σ is a free variable in the set of finite sequences over E which includes the empty sequence Nil. The map f is a solution for y that extracts values from the sequence as state advances (as events are appended to the event sequence). Essentially y depends on two parameters: the sequence and the map. These parameters are often implicit.
- By the definition of state variable, y(Nil) = f(Nil) and represents the value of y in the initial state of the system.
- And y(z) = f(z) is the value of y in the state determined by sequence z.
- The convention here is that σ is always a free variable over E* (the set of finite sequences over alphabet E). Since y=f(σ) for any w, then y = y(σ), trivially. And I mean this is a trivial, tautological equation, but it’s going to be useful. We can think of y as the value of the state variable in the current state and σ as the current sequence.
- If z is a sequence and e is an event or a variable over events, write ze for the sequence obtained by appending e to z on the right. So y(σe) is the value of y in the “next state” – the state after e. If y = y(σe) then the event “e” leaves the value of y unchanged. If y(σe) = y +2 then the event “e” increases the value of y by 2. Note that if y=f(σ) then y(σe) = f(σe) so (y(σe)=y+2) can be rewritten as f(σe)=f(σ) + 2. I’m sorry to have to explain basic familiar algebra, but this work is an attempt to express properties of systems in, mostly, basic familiar algebra.
- Suppose we define y by a pair of equations: y(Nil)= k and y(σe) = h(e, y). If k is some constant and h is a defined map, then we have implicitly defined f on every finite sequence and so implicitly defined y in every state. Note y(Nil) = f(Nil)=k. And y(σe)= h(e,y) = h(e,y(σ)) = h(e,f(σ)). Suppose sequence w=<a,b.c>, then y(w) = f(<a,b,c>) = h(c,f(<a,b>)) = h(c,h(b,f(<a>)) = h(c,h(b,h(a,k)))). But in many cases we don’t want to specify f completely – f is a solution to the constraints on y and there may be many or infinitely many or no solutions.
- For example, suppose C is a state variable and C(Nil)=0 and C(σe) = 1+C. Then we have defined C to be the length of the event sequence. Define L(σ e) = e so L is the most recent event. Define SinceReset(Nil) = 0 and SinceReset(σ e) = 0 if e=RESET and SinceReset(σ e) = 1+ SinceReset otherwise.
- Suppose we have a state variable Clock that somehow determines time passed since initial state from the event sequence. Then (Clock(σ e)- Clock) is the duration of event e in this state (and may be different for different values of σ ). Define waitingp(Nil)=0 and waitingp(σ e)=0 if Executing(σ e) =p and waitingp(σ e)= waitingp+(Clock(σ e)- Clock) otherwise. So waitingp is the time that process p has been waiting to become the executing process. We might want a property (waitingp > k only if priorityp < priorityq where q = Executing).
- Ok, now we can consider component composition – which is just function composition. Consider the state variable L defined above and suppose we have a new alphabet B consisting of Leftx and Rightx for x in some set X of data values. The goal is to construct a shift register from n instances of L. Define Li = L(ui) where ui is also a state variable that has values that are sequences of elements of X. Usually we want components to state in the initial state so ui(Nil) = Nil. This means Li (Nil)= L(Nil) which is something we didn’t define. Now define ui(σ e) where “e” is an element of the event alphabet of the composite system. Remember I’m using juxtaposition to mean “append on the right” so, for example (uiLi-1 ) means “append the current value of Li-1 to the current value of ui”
- ui(σ e)= uix if i=1 and e=Rightxor if i=n and e=Leftx
- ui(σ e)= ui Li-1 if i>1 and e=Leftx
- ui(σ e) = ui Li+1 if i<n and e=Rightx
- The definition above causes all n components to change state in parallel when an event is appended to the event sequence of the composite system – but it’s just composition: Li = L(ui)= L(ui(σ)) and Li (σ e)= L(ui(σ e) ) =L(ui(σ) e’) ) = e’ where e’ is calculated as above.
- If a device D that is constructed from components C1 … Cn that are interconnected. D is a function of σ but the components don’t “see” the same events. For each component, we need a state variable ui that is sequence valued and a function of σ . Usually these are defined recursively general ui(Nil) = Nil and ui(σe) = concatenate(ui, hi(e,C1(u1 )… Cn(un ))) or something like that. That is, each event e for D causes event or events hi(e,C1(u1 )… Cn(un )) to be appended to the sequence of events ui,.