(ref-event-fx
:switch-to-articles-panel
(fn [{:keys [db]} _]
;; Note the following :http effect
{:http {:action :GET
:url "http://api.something.com/articles/"
:path [:a :path :within :app-db]}}))
Don’t use this library - the code is not ready.
A re-frame library for performing HTTP requests using an effect with key :http
It is an improved version of the original re-frame-http-fx library
HTTP requests are simple, right?
You send off a request, you get back a response, and you store it in app-db
.
Done.
Except requests are anything but simple. There is a happy path, yes, but it winds through a deceptively dense briar thicket of fiddly issues. Accessing unreliable servers across unreliable networks using an async flow of control is hard enough, what with the multiple failure paths and possible retries but, on top of that, there’s also the cross-cutting issues of managing UI updates, user-initiated cancellations, error recovery, logging, statistics gathering, etc.
Many programmers instinctively shy away from the tentacles of this complexity, pragmatically preferring naive but simple solutions. And yet, even after accepting that trade-off, they can find themselves with an uncomfortable amount of repetition and boilerplate.
This library has two goals:
proper treatment for failure paths (robustness and a better user experience)
you write less code for each request (no repetition or fragile boilerplate)
These two goals might typically pull in opposite directions, but this library saves you from the horns of this dilemma and seeks to let you have your cake and eat it too. In fact, maybe even eat it twice. But no chocolate sprinkles. We’re not monsters.
Here’s a re-frame event handler returning an effect keyed :http
(see the last three lines):
(ref-event-fx
:switch-to-articles-panel
(fn [{:keys [db]} _]
;; Note the following :http effect
{:http {:action :GET
:url "http://api.something.com/articles/"
:path [:a :path :within :app-db]}}))
That :http
effect will do an HTTP GET and place any response data into
app-db
at the given :path
.
But wait, there’s more.
This request will be retried on timeouts, and 503
failure, etc. Logs will be written, errors will be reported, and interesting
statistics will be captured. The user will see a busy-twirly and be able to
cancel the request by hitting a button. The response data can be processed from
JSON and transit into EDN, before being put into right place within app-db
. Etc.
So, by using just three lines of code - simple ones at that - you’ll get a robust HTTP request process. However, as you’ll soon see, there’s no magic happening to achieve the outcome. You’ll need to compose the right defaults.
The cost? Some upfront learning so you can fashion these right defaults for your application. Let us begin that learning now …
I have some good news and some bad news — first, the good news.
This library models an HTTP request using a Finite State Machine (hereafter FSM) - one which captures the various failure paths and retry paths, etc. That leads to a robust and understandable request process.
Also, this library wraps fetch
, making it central to the FSM, and that delivers a
browser-modern way to do the network layer.
But, that’s the end of the good news. The bad news is that you’ll be doing most of the work.
Most parts of the FSM are left unimplemented, and you will need to fill in these blanks by writing a number of Logical State Handlers.
A Logical State Handler
provides the behaviour for a single
Logical State in the FSM. To help you, this library will provide "a recipe"
for each of these handlers, describing what your implementation might reasonably
do. So, for example, in the Failed
state, the recipe might
say that you "perform error logging and error reporting to the user" and "recover the application
into a stable state, in light of the failure".
Of course, only you, the application programmer, know how to implement "a recipe" correctly for your application. And that’s precisely why the FSM’s implementation is left incomplete and why you are asked to fill in the blanks.
With a bit of squinting and head tilting, you can see a
Logical State Handler as something of a callback. For example, writing a
Logical State Handler for the Failed State is very similar to supplying an
on-failure callback. Except, of course, because we’re in
re-frame land, the mechanism will be more dispatch-back than callback .
|
Each Logical State Handler
you write has to be something of a "good citizen"
within the overall composition (follow the task recipe). If your
Logical State handler
fails to do the right thing, any FSM you compose using
it will be, to some extent, dysfunctional. I have every confidence in you.
Later, once you have written the necessary set of Logical State Handlers
, you
compose them to form a functioning FSM. And, because this is a ClojureScript
library, this composition happens in a data oriented way.
If your application’s needs are sufficiently complicated, you can create multiple FSM compositions within the one app, with each configuration designed for a different kind of request.
And, if you use one regularly, you can nominate it to be
the default FSM. That three-line example I presented earlier owes its brevity to
(implicitly) using a default
FSM composition.
Finally, let’s talk about state.
XXX logical state
XXX extended state
Each FSM instance has some
working state, which we call request-state
. In addition, there will be
some state stored within app-db
which is also associated with a
request - we call that path-state
. Your
Logical State handlers`are responsible for pushing/projecting parts of
`request-state
through into path-state
, so that your UI can render the state of
the request. Again, only you, the application programmer, know how you want this
done, although we will certainly be suggesting some patterns. For
example, you might write a :retrying?
value of true
into path-state
which then
causes the UI to render "Sorry about the delay. There was a problem with the
network connection, so we’re trying again".
So, in summary:
you should get to know the FSM topology (in the next section)
you will implement the blank parts of the FSM but writing a set of
Logical State handlers
, following the recipe for each.
you will compose an ensemble of these Logical State handlers
to form a FSM
your FSM will likely push/project aspects of request-state
into path-state
you will write views in terms of what’s in path-state
, to show the
user what’s happening
when you make an HTTP request, you’ll be nominating which FSM to use (or you will implicitly be using your default FSM)
An HTTP request is a multi-step process which plays out over time. This library models this process as a Machine.
By a Machine, I’m referring to the abstract concept of something which does something. This library uses a specific kind of Machine called a *Finte State Machine (FSM)*, which is one that has a fixed, finite number of States.
In each State, a Machine has discrete responsibilities, concerns and behaviours. And a FSM can only be in one State at a time.
This library formalises that process as a Machine. And, specifically, it uses a Finte State Machine (FSM), which has a fixed, finite number of States.
States allow us to reason about what a Machine is doing because: * * in each State the Machine has discrete responsibilities, concerns and behaviours
So, when an FSM changes State, it goes from doing one thing to doing another thing.
Over time, events occur and they can cause the Machine to changes from one State to another. Such events are called Triggers and a change in State is called a Transition. Sometimes there are certain Actions (behaviour/computation) associated with a Transition. But, to repeat, the significant thing about a State change is that the Machine goes from doing one thing to doing another thing.
it can do nothing (waiting for a Trigger)
it can undertake "an activity" which takes finite time and comes to an end (this ending might causes a Trigger)
it can undertake "an activity" which does not naturally come to an end (until there is a Trigger)
it can compute the next Trigger. These are sometimes called PseudoStates.
This library’s FSM contains examples of all these kinds of State.
The Logical State Handlers
you will be asked to write are about "doing a thing" when the Machine is in a
particular State. And, as a result, they implement the behaviour for one part of this library’s FSM.
The FSM at the core of this library:
Notes:
to use this library, you’ll need to understand this FSM
the boxes in the diagram represent the FSM’s Logical States
the lines between the boxes show what Transitions are allowed between Logical States
the names on those lines are the Triggers (the event which causes the Transition to happen)
when you write a Logical State Handler
you are implementing the behaviour
for one of the boxes
the "happy path" for a request is shown in blue (both boxes and lines)
and, yes, there are variations on this FSM model of a request - this one is ours. We could, for example, have teased the "Problem" Logical State out into four distinct states: "Timed Out", "Connection Problem", "Recoverable Server Problem" and "Unrecoverable Server Problem". We decided not to do that because of, well, reasons. My point is that there isn’t a "right" model, just one that is fit for purpose.
NOTE:
Earlier, we saw this code which uses an effect :http
to initiate an HTTP GET request:
(ref-event-fx
:switch-to-articles-panel
(fn [{:keys [db]} _]
;; Note the following :http effect
{:http {:action :GET
:url "http://api.something.com/articles/"
:path [:put :response :data :at :this :path :in :app-db]}}))
Who doesn’t love terse? But, as a learning exercise,
let’s now pendulum to the opposite extreme
and show you the most verbose use of the
:http
effect:
(reg-event-fx
:request-articles
(fn [_ _]
{:http {:action :GET ;; can be :PUT :POST :HEAD etc
:url "http://api.something.com/articles/"
;; Optional. The path within `app-db` to which request related data should be written
;; The map at this location is known as `path-state`
:path [:a :path :within :app-db]
;; Compose the FSM by nominating the `Logical State handlers`.
;; Look back at the FSM diagram and at the boxes which represent
;; Logical States.
;; When the FSM transitions to a new Logical State, it will `dispatch`
;; the event you nominate below, and the associated event handler is expected
;; to perform "the behaviour" required of that Logical State.
:fsm {:in-setup [:my-setup]
:in-process [:my-processor]
:in-problem [:deep-think :where-did-I-go-wrong]
:in-failed [:call-mum]
:in-cancelled [:generic-cancelled]
:in-succeeded [:yah! "fist-pump" :twice]
:in-teardown [:so-tired-now]}
;; a map of query params
:params {:user "Fred"
:customer "big one"}
;; a map of HTTP headers
:headers {"Authorization" "Bearer QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
"Cache-Control" "no-cache"}
;; Where there is a body to the response, fetch will automatically
;; process that body according to mime type provided.
;; XXX Isaac we have to explain this
;; XXX are there sensible defaults? What if I forget to provide?
:content-type {#"application/.*json" :json
#"application/edn" :text}
;; Optional - by default a request will run as long as the browser implementation allows
:timeout 5000
;; Note: GET or HEAD cannot have body.
;; Can be one of: String | js/ArrayBuffer | js/Blob | js/FormData | js/BufferSource | js/ReadableStream
:body "a string"
;; how many times should occurances like timeouts or HTTP status 503 be retried before failing
:max-retries 5
;; Optional: an area to put application-specific data
;; If data is supplied here, it will probably be used later within the
;; implementation of a "Logical State Handler". For example "description"
;; might be a useful string for displaying to the users in the UI or
;; to put in errors or logs.
:context {:description "Loading articles"
:dispatch-on-success [:another event]
:recover-to {[:where :I :store :the :panel :id] old-value}}
;; The following are optional and more obscure.
;; See https://developer.mozilla.org/en-US/docs/Web/API/Request#Properties
:credentials "omit"
:redirect "manual"
:mode "cors"
:cache "no-store"
:referrer "no-referrer"
;; See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
:integrity "sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE="}))
While all this specification offers useful flexibility, we clearly don’t want to repeat
this much every time. Particularly because
we’ll often want the same headers, params and Logical State handers
.
How do we avoid boilerplate and repertition?
A Profile associates an id
with a fragment of :http
specification.
You "register" one or more Profiles, typically on application startup.
Because an :http
specification is just data (a map), a fragment is also
just data (again, a map). And if you think that sounds pretty simple, you’d be right.
The code below shows how to register a profile with id :xyz
, and associate
it with certain specification values:
(reg-event-fx
:register-my-http-profile
(fn [_ _]
{:http {;; The `:action` is no longer a verb
;; Instead it indicates we are registering a profile
:action :reg-profile
;; This identifier will be used later
:id :xyz
;; Optional. Set this profile as the 'default' one?
:default? true
;; This is the important bit
;; This map captures the values associated with this profile.
:values {:url "http:/api.some.com/v2"
:fsm {:in-process [:my-processor]
:in-problem [:generic-problem :extra "whatever"]
:in-failed [:my-special-failed]
:in-cancelled [:generic-cancelled]
:in-teardown [:generic-teardown]}
:timeout 3000
:max-retries 2
:context {...}}}}))
Here’s an example of using the Profile with id :xyz
which we registered above:
{:http {:action :GET
:url "http://api.endpoint.com/articles/"
:path [:somewhere :in :app-db]}
Wait! Is this a trick? That’s the same three lines as before!
Indeed. If you look back, you’ll see the Profile :xyz
was registered with :default? true
which means it will be used by default, but only if no Profile is explicitly provided.
Here’s how to explicitly nominate a Profile:
{:http {:action :GET
:url "http://api.endpoint.com/articles/"
:path [:somewhere :in :app-db]
:profiles [:xyz]}} ;; <--- NEW: THIS IS HOW WE SAY WHAT PROFILE(S) TO USE
That new key :profiles
allows you to nominate a vector of previously registered Profile ids
. The map
of :values
from those Profiles will be added into the :http
specification.
Here’s another example, but this time with multiple profile ids:
{:http {:action :GET
:url "http://api.endpoint.com/articles/"
:path [:somewhere :in :app-db]
:profiles [:jwt-token :standard-parms :xyz]}} ;; <---- MULTIPLE
The map of :values
in all nominated profiles will be composed into the
the :http
specification.
explicitly using :profiles [] would mean no profiles. This is the way to NOT even use the default.
|
How are multiple profiles combined?
As a first approximation, imagine the process as a clojure.core/reduce
across a collection of maps, using clojure.core/merge
:
(reduce merge [map1, map2, map3])
This will accumulate the key/value pairs in the maps, into one final map.
An example:
(def map1 {:a 1})
(def map2 {:b 11})
(reduce merge [map1, map2])
the result is {:a 1 :b 11}
.
Instead of map1
, map2
, imagine that we
combine profile1
, profile2
, like this:
(def profile1 {:action :GET})
(def profile2 {:url "http://some.com/"})
(reduce merge [profile1, profile2])
with the result:
{
:action :GET
:url "http://some.com/"
}
While ever the profiles have disjoint keys, this is straightforward. But, when there are duplicate keys, we need a strategy to "combine" the coresponding values.
if both values satisfy str?
, then they will be combined with str
if both values satisfy set?
, then they will be combined with clojure.set/union
if both values satisfy map?
, then they will be combined with merge
(remember merge is shallow).
if both values satisfy sequential?
, then conj
is used
otherwise, last value wins (no combining)
Imagine we have a special version of merge
which implements these rules, called say special-merge
.
(def profile1 {:url "http://some.com/"})
(def profile2 {:url "blah"})
(reduce special-merge [profile1, profile2])
the result would be:
{:url "http://some.com/blah"}
because the values for the duplicate :url
keys are strings, they will be combined with str
to form one string.
Similarly:
(def profile1 {:params {:Cache-Control "no-cach"}})
(def profile2 {:params {:Authorization "Basic YWxhZGRpbjpvcGVuc2VzYW1l"}})
(reduce special-merge [profile1, profile2])
the result would be:
{:params {:Cache-Control "no-cach"
:Authorization "Basic YWxhZGRpbjpvcGVuc2VzYW1l"}}
because the values for the duplicate :params
keys are maps and will be combined with merge
.
So, when you nominate multiple profiles:
{:http {:action :GET
...
:profiles [:jwt-token :standard-parms :xyz]}} ;; <---- MULTIPLE PROFILES
the final :http
spec will be a map. And it will be as if it was formed
using special-merge
on all the :values
maps from all the nominated profiles,
plus the map supplied for the :http
itself as the last one.
Where you need to take detailed control of the "combining" process you
can use this library’s API function merge-profiles
{:http (-> (merge-profiles [:xyz :another]) ;; combines these two profiles and returns a map
(assoc-in [:fsm :in-setup] [:special]) ;; now manipulate the map in the way you want
(update-in [:url] str "/path"))
The function call (http/merge-profiles [:xyz])
would just return
:values
map for that one profile.
There are two kinds of State:
request-state
is data for a single request and it maintained by this library.
It only exists for the lifetime of a request.
This state is stored internally in the library and, although it is
provided in the event vector of Logical State Handlers, it is effectively
read-only.
It includes the
request id, the current logical state of the FSM, the original request,
a trace history through the FSM including timings, etc.
path-state
- this state is a map of values which exists at a particular
path within app-db
. It is the application’s "materialised
view" of the request-state
.
The contents of this map is up to you,
the writer of the application. It will be created and maintained by the
Logical State Handlers you write.
Typically, the in-setup
LogicalStateHandler initialises path-state
, and it is
then maintained across the request handling process by the various FSM handlers. Ultimately, it
will contain the response data or an error. Your views will be subscribed to this map and will
render it appropriately for the user to view.
An example of the path-state
map.
{
:request-id 123456
:loading? true
:result nil
:retries 0
:cancelled? false
:description "Loading filtered thingos"
:error {
:title "Error loading thingos"
:what-happened "Couldn't load thingos from the server because it returned a status 500 error"
:consequence "This application can't display your thingos"
:users-next-action "Please report this error to your help desk via email, with a screenshot. Perhaps try again later"}
}
Remember, you design this map. You initialise it in in-setup
. You update it to reflect the state of the ongoing request. You create the subscriptions which deliver it to a view, and that view will render it.
XXX :context is put where?
Note: none of this precludes you, for example, writing errors to a different place within app-db. You write the LogicalStatehandlers. Your choice about how data flows into app-db
. The proposal above is just one way to do it.
XXX To avoid race conditions, should the booleans be false in absence via subscriptions? Eg: use completed?
instead of loading?
because "absence" (a nil) correctly matches the predicate’s negative value.
XXX consider what else needs to happen to work well with re-frame-async-flow
So, I’d like to stress two points already made:
- lifetime: path-state
exists for as long as your application code says it should - it persists. Whereas
request-state
is created and destroyed by this library - it is a means to an ends - it is transitory.
- during the request process, request-state
tends to be authoritative. : path-state
is something
of a projection or materialised view of request-state
. (Not entirely true but a useful mental model at
this early stage in explanation)
While path-state
…. there might need to be a :loading?
value set to true to indicate that the busy twirly should be kept up. Or perhaps a :retrying?
flag might need to be "projected" from the reguest-state
so that, again, the UI can show the user what is happening.
Ultimately, the most important part of this path-state
is the (processed) response data itself. But there will be other information alongside it. For this reason, presentation-state
is normally a map of values with a key for response
, but it has other values.
The path-state
is managed by your Logical State Handlers
. You control what data is projected from the request-state
across into the presentation-state
. Because you, the application programmer, knows what you want to set within app-db
. You know how you want the UI to render the state of the request process.
For example:
- it is the job of the in-setup
to initially create the XXX-state
assumed to be a map.
And it might initially establish within this map a :loading?
flag as true
.
- it is then the job of the in-teardown
handler to set the :loading?
flag back to false
(thus taking down the twirly).
design path-state
and the views which render it (or simply use the default design suggested)
implement your Logical State Handlers (or simply use the default Handlers provided)
The Logical State Handlers you write are about "executing the behaviour" associated with being in a particular state within the FSM. They implement behaviour for one part of "the machine".
Recipes for each of the Logical State Handlers …
Overview: prepare the application for the pending HTTP request.
establish initial path-state
at the nominated :path
optionally, if the application is to allow the user to cancel the request
(e.g., via a button) then capture the :request-id
of the request and assoc it
into path-state
for access within the view (which will dispatch a cancel request event with this id supplied).
optionally, put up a twirly-busy-thing, perhaps with a description of the request: "Loading all the blah things", perhaps with a cancel button
optionally, cause the application to change panel or view to be ready for the incoming response data.
trigger :send
to cause the transition to waiting
state. The transition will cause the fetch
action which actually initiates the request.
Views subscribed to this path-state
will then render the UI, probably locking
it up and allowing the user to see that a request is in-flight.
XXX a panel might change …. perhaps the user clicked a button to "View Inappropriate", so the application will change panels to the inappropriate one (via a change in app-db
state), AND also kickoff a server request to get the "inappropriates".
Example implementation:
(fn [{:keys [db] :as cofx} [_ {:keys [request-id context] :as request-state}]]
(let [path (:path context)]
;; trigger for state transition
{:http {:trigger :send
:request-id request-id}
;; Initialise app-db to reflect that a request is now inflight
;; This might mean updating some "global" place in app-db to get a twirly-busy-thing up
;; This might mean putting an "map" at the path provided in the request
:db (-> db
(assoc-in (conj path :request-id) request-id)
(assoc-in [:global :loading?] true)
(assoc-in [:global :loading-text] (:loading-text context)))}))
XXX once preparation is complete, notice that your code is expected to trigger
the transition.
This State Handler is unique because it is the only one you can’t write. It is provided by this library.
In this state, we are waiting for an HTTP response (after the fetch
is
launched) and then doing the first round of processing of the response body.
Process the response: turn transit JSON into transit or XXX
store in app-db
FSM trigger :processed
or :processing-error
Example implementation
(fn [{:keys [db] :as cofx} [_ {:keys [request-id response context] :as request-state}]]
(let [path (:path context)
reader (transit/reader :json)]
(try
(let [data (transit/read reader (:body response))]
{:db (assoc-in db (conj path :data) data)
:http {:trigger :processed
:request-id request-id}}))
(catch js/Error e
{:db (-> db
(assoc-in (conj path :error) (str e)))
:http {:trigger :processing-error
:request-id request-id}})))
XXX :processing-error
causes a transition to failed
. How and where does this state obtain the error details?
The processing of the response has succeeded.
FSM trigger :done
Example implementation
(fn [{:keys [db] :as cofx} [_ {:keys [request-id] :as request-state}]]
{:http {:trigger :done
:request-id request-id}})
decide what to do about the problem - retry or give up?
FSM trigger :fail
or :retry
Example implementation:
(fn [{:keys [db] :as cofx} [_ {:keys [request-id context problem response] :as request-state}]]
(let [path (:path context)
temporary? (= :timeout problem)
max-retries (:max-retries context)
num-retries (get-in db (conj path :num-retries request-id) 0)
try-again? (and (< num-retries max-retries) temporary?)]
(if try-again?
{:http {:trigger :retry
:request-id request-id}
:db (update-in db (conj path :num-retries request-id) inc)}
{:http {:trigger :fail
:request-id request-id}})))
network connection error - no response - retry-able (except that DNS issues take a long time, so retires are annoying)
cross-site scripting whereby access is denied; or
requesting a URI that is unreachable (typo, DNS issues, invalid hostname etc); or
request is interrupted after being sent (browser refresh or navigates away from the page); or
request is otherwise intercepted (check your ad blocker).
fetch
API body processing error; e.g. JSON parse error.
timeout - no response - retry-able
non 200 HTTP status - returned from the server - MAY have a response
may have a response :body returned from server which will need to be processed. See https://tools.ietf.org/html/rfc7807 Imagine a 403 Forbidden response. XXX talk about how it might be EDN or a Blob etc.
some HTTP status are retry-able and some are not
The request has failed and we must now adjust for that.
Ultimately, it doesn’t actually matter why we are in the failed state, but to help give context, here’s the sort of reasons we end up in this state:
* no outright failure, but too many retries (see :history
XXX for what happened)
* some kind of networking error happened which means the request never even got to the target server (CORS, DNS error?)
* the server failed in some way (didn’t return a 200)
* a 200 response was received but an error occurred when processing that response
log the error
show the error to the user
put the application back into a sane state
FSM trigger :teardown
Example implementation:
(fn [{:keys [db] :as cofx} [_ {:keys [request-id context problem response] :as request-state}]]
(let [path (:path context)]
{:http {:trigger :teardown
:request-id request-id}
:db (-> db
...)}))
This state follows user cancellation.
put the application into a state consistent with the cancellation. What does the user see? What can they do next?
update path-state
, maybe.
FSM trigger :teardown
Example implementation:
(fn [{:keys [db] :as cofx} [_ {:keys [request-id context problem response] :as request-state}]]
(let [path (:path context)]
{:http {:trigger :teardown
:request-id request-id}
:db (-> db
...)}))
Irrespective of the outcome of the request (success, cancellation or failure), this state occurs immediately before it completes.
As a result, in this state we handle any actions which have to happen irrespective of the outcome.
take down the twirly
accumulate and log final stats
possible updates to path-state
change :loading?
to false
possible updates to app-db
busy twirly removal
FSM trigger :destroy
Example implementation:
(fn [{:keys [db]} [_ {:keys [request-id context] :as request-state}]]
(let [path (:path context)]
{:http {:trigger :destroy
:request-id request-id}
:db (-> db
(assoc-in [:global :loading?] false))}))
split the recipies into their own docs in /docs
FAQ for file upload - reference example application
Talk about the two approaches to switching tabs
Nine states of UI
note somewhere you can supply multiple requests … a vector
Add note that fetch
doesn’t work on IE. So you’ll need to provide a polyfil if you target IE.
add optional :cancel
event handler ??
??? add an interceptor to assert the correctness of the Transitions - Logical State Handlers
anything we should be doing around stubbing and testing?
add trace to FSM
Your FSM is wrong
Why don’t you use gards?
A control system determines its outputs depending on its inputs.
If the present input values are sufficient to determine the outputs the control system is a combinatorial system.
For example, a traffic light control system could be constructed like this: * the only input is the current time (the input changes every second) * strip the input time down to just the seconds part (a number between 0 and 59) * Apply the following rules: if the seconds is between 0 and 27 seconds then the output is "green" if the seconds is between 28 and 33 seconds, the output is "orange" ** if the seconds is between 34 and 59 seconds, the output is "red"
At any point in this system’s fucntioning, knowing the input (time) is sufficient to know the output (green,orange,red).
If, on the other hand, the control system needs to know the history of its inputs to determine its output the system is a sequential system.
To function, a sequential system must store a representation of its input history. And this representation is known as State.
Imagine a traffic lights control system which XXX
If the State was a 16 bit integer, the number of States would be 65536.
A state machine is the oldest known formal model for sequential behaviour i.e. behaviour that cannot be defined by the knowledge of inputs only, but depends on the history of the inputs.
Can you improve this documentation? These fine people already did:
Mike Thompson, Isaac Johnston & Denis JohnsonEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close