Summary: Not all coding is sequential. For non-specialists, the programming language is often a barrier. Whand is a simplified declarative language, intended to control external devices as a function of events and the passage of time. A Whand script essentially describes the desired result, unlike sequential languages that focus more on the means than on the goal. In Whand, many operations leading to the goal are performed implicitly. Its syntax evokes a natural language and is relatively easy to understand and to customize. Whand is also parallel, making objects independent of one another and facilitating debugging. Developers are welcome to participate in the open-source Whand community.

With the much heralded advent of the “Internet of Objects” in the coming years, the general public will be confronted with the need for coding. This is obviously a daunting prospect for the layman. For instance, how to set up the fridge so that it automatically re-supplies? Apart from the intrinsic complexity of the task, the programming language itself contributes to the difficulty of coding. By definition, a computer code does not lend itself to easy reading, as can be attested by anyone who ever attempted to decipher someone else’s programming, or even to revisit one’s own code after a few months. The syntax and boilerplate of a language may baffle a neophyte. The order of instructions is usually critical. Multiple conditional statements may obscure the structure of a program. In addition, short, equivocal variable names are often used for practical reasons. Of course, all programs need to be clarified by comments (hereafter indicated by #).

To clear some of these hurdles, I designed an original language called Whand. Whand is specifically directed towards non-programmers who need a way to control simple devices. Whand is a goal-driven language, meaning that it focuses on the goal rather than the means. Its syntax is simplified to the extreme and evokes a natural language so it is relatively easy to understand what function is realized. Whand is not procedural but declarative, so instructions need not be in any specific order. Whand automatically manages states, transitions and delays and it provides convenient built-in functions to manipulate lists of objects. Whand is also fully parallel, to simultaneously control multiple appliances without trouble.

1. Goal-driven vs. procedural languages

The goal of a program is either a state or an action. Objects are expected to have constant values, or states, over various time intervals. Standard, procedural languages tend to focus on actions that change states, whereas Whand tends to consider states as goals. A state may be for instance the fact that a light is on. This goal involves actions to switch the light on or off.

Consider for instance the following specifications in a quasi-natural language:

Switch on light when daylight is less than daylight threshold.

Switch off light when daylight is more than daylight threshold.

Assuming a daylight threshold set to 5 arbitrary units, this could translate to Whand as:

light_is_on: daylight < daylight_threshold # defines state ‘light_is_on’

daylight_threshold: 5

daylight: measure 2 # an input (keyword: ‘measure’)

output 1: light_is_on # an output (keyword: ‘output’)

As can be seen, the Whand script closely resembles the initial specifications. The goal (the light is on) is stated up front. It depends on two causes: the level of daylight and the threshold value (Figure 1). Here, condition “daylight < daylight_threshold” amounts to the following Whand instructions:

light_is_on when daylight < daylight_threshold # defines state ‘light_is_on’

until daylight >= daylight_threshold # with keywords ‘when’ and ‘until’

The “when” condition results in light on, the “until” condition results in light off. A condition is a logical event that is true or false at a given instant.

With Whand, boilerplate is kept to a strict minimum and there is no need to further specify how the goal should be achieved. The output 1 instruction directly links the internal variable “light_is_on” to the hardware controlling the light (line 1). The measure 2 instruction assigns name “daylight” to the input from a particular measure instrument (connected to line 2). Moreover, in Whand, the order of the various instructions is indifferent.

We can compare this to a standard, procedural language, here in pseudocode:

Procedure control_light:

set daylight_threshold to 5

set light_state to off

repeat: {

measure daylight

if daylight < daylight_threshold and light_state is off:

switch light on

set light_state to on

if daylight >= daylight_threshold and light_state is on:

switch light off

set light_state to off

}

This code appears slightly more complex, even though procedures to measure daylight and to switch light on or off are not detailed. We need to explicitly set up a loop to test the level of daylight and compare it to the threshold (Figure 1). Indentation and/or curly brackets delimit the loop. The loop indefinitely repeats. We must also keep track of the state of the light.

Figure 1. Goal-driven vs. procedural approaches. Left panel: In a goal-driven language such as Whand, the goal to switch the light on or off immediately depends on two conditions relating the two causes: daylight level and daylight threshold. The measure of daylight is permanent. Daylight threshold is constant. The “when” condition results in light on, the “until” condition results in light off. All instructions are processed in parallel. Right panel: In a procedural language, processing is sequential and follows a loop structure. The organization and order of instructions is critical. All processing takes place within a general loop which repeats indefinitely. One must test both the level of daylight and the state of the light before taking action to switch the light on or off. These actions rely on procedures which then return to the main loop.

This simple example illustrates the difference in philosophy between goal-driven and procedural languages. Indeed, the two approaches are profoundly different.

The goal-driven language declares the goal at the top. It then follows a top-down approach to specify causes that will result in the achievement of this goal. Each cause may be defined as a function of lower causes. The details of the implementation are left to the system, with minimal specifications. The goal is given as a state, which means that the system internally knows whether the light is on or off and whether any action is needed to change the state. No particular sequence or structure of instructions is required. Because processing is parallel, the code for another appliance can simply be added before or after the current code without any risk of interference. Moreover, the code may be re-used or customized relatively easily.

The procedural language relies on a specific sequence of instructions. The loop structure must be explicit, whereas in Whand it is implicit. The calls to procedures light_on and light_off constitute the actual goal of the code, but they are somewhat buried inside. As discussed in the following section, the state of each appliance must be tested before deciding to switch it on or off. Moreover, the code must continuously test the daylight level and cannot leave the loop. If one wants to control another appliance, the new code must be incorporated within the general loop. Whatever language is used, the loop is assumed to run fast enough to avoid noticeable delays in the control processes, and it should not stop to wait for an event because this would suspend all other processing.

2. States and transitions

In the procedural approach, we keep track of the state of the light to test it before taking any action. This step is critical if, for instance, the goal is to order food to replenish the fridge. One must send the order once and not repeat it while the fridge is still depleted and we wait for delivery. But the test will be performed on each iteration of the loop. Thus, our trigger must be the transition from full to depleted and not the depleted state itself.

With the procedural approach, this can be performed within the general loop by explicitly comparing the current state to the preceding state and then updating the state, and/or by keeping track of the execution of the action, here milk order and delivery:

Procedure order_milk:

set milk_threshold to 2

set milk_order to none

repeat: {

measure milk_level

if milk_level < milk_threshold and milk_order is none:

order milk

set milk order to pending

if milk_delivery and milk_order is pending:

set milk_order to none

}

How milk level is evaluated will not be specified here. Note that the state of milk_order must be explicitly adjusted: it is initialized to “none”, set to “pending” as soon as the order is passed, and reset to “none” upon delivery. None of these steps may be omitted.

In Whand, this is not necessary because the “when” and “until” conditions only respond to changes. Specifically, they only respond to the transition of the specified expression, a logical event, from false to true, or in electronics terms, to a rising edge.

milk_threshold: 2

milk_ordered when milk_level < milk_threshold

until milk_delivery

Here the expression controlling “when” is “milk_level < milk_threshold“. The state “milk_ordered” will only pass an actual order on the rising edge of this expression, i.e. when it becomes true, and the state will remain true afterwards. There is no risk of passing another order until “milk_ordered” returns to false upon delivery. The transition to false does not trigger any action by itself, but only enables the occurrence of a new rising edge. Of course, delivery will increase milk level above threshold, so the expression “milk_level < milk_threshold“ will also return to false. Thus, we could use an even shorter formulation:

milk_threshold: 2

milk_ordered: milk_level < milk_threshold

Here, there is not even a “when” condition. The term on the left “milk_ordered” continuously tracks the value of the expression on the right “milk_level < milk_threshold“. Its rising edge will trigger the order. Admittedly, this shorter form may appear somewhat less explicit. Still, both forms let the user focus on the state of the goal and its transitions. They do not require the precise combination of instructions characteristic of the procedural language.

3. Timing and delays

In the preceding example, note that there was a period of undefined duration between the order and the delivery of milk. Surely, beyond a certain time duration, further action may be needed to rapidly get milk. In the context of automation, one often needs to compute time and delays. A delay is triggered by an event or condition, and then, after the specified time has elapsed, some action will be taken. For instance, the microwave oven will stop heating, or the light in the basement will switch off. These delays are often programmed into the hardware, but sometimes, more flexibility is needed, for instance if some action needs to be repeated at regular intervals.

Delays come in two flavors: non-resetting delays and resetting delays. A non-resetting delay ignores triggering events that occur during the delay. Microwave cooking is an example of non-resetting delay because the cooking duration is fixed once started (except when prematurely stopped). By contrast, a resetting delay computes time with respect to the last occurrence of the triggering event. A basement light controlled by a movement detector is an example of resetting delay because we want the light to persist for some duration, but also to remain on as long as movement occurs.

Whand handles delays effortlessly:

cooking_duration: 2 min

microwave_on: when button_pressed # off at start by default

until microwave_on + cooking_duration # non-resetting delay

basement_light_duration: 30 s

basement_light: when movement_detected # off at start by default

until basement_light_duration since movement_detected # resetting delay

Note that “microwave_on + cooking_duration” adds a delay to a logical event. This creates an internal event which is automatically delayed by the specified duration and then acts as a condition for the “when”or “until” clause. The “since” operator is similar except for the resetting mechanism.

Procedural programming needs a clock that provides current time. The instruction flow must be carefully designed:

Procedure timers:

set cooking_duration to 2 min

set basement_light_duration to 30 s

set microwave_state to off

set basement_light_state to off

repeat: {

read clock_time

if button_pressed and microwave_state is off: # ignore button while on

start microwave

set microwave_state to on

store clock_time as microwave_t0

if clock_time — microwave_t0 > cooking_duration and microwave_state is on:

stop microwave

set microwave_state to off

if movement detected: # do not ignore movement

store clock_time as basement_t0

if basement_light_state is off:

switch on basement light

set basement_light_state to on

if clock_time-basement_t0>basement_light_duration and basement_light_state is on:

switch off basement light

set basement_light_state to off

}

Here, each timer needs its own start time memory t0. This value is then compared to the clock time on each iteration of the loop. The code also keeps track of the state of the microwave and of the basement light and uses them in the form of composed if statements.

Composed statements are also available under Whand. For instance, the following code controls a repetitive warning sound. Of course, this basic structure applies to any type of repetitive event with periods ranging from hundreds of milliseconds to hours or days:

sound_duration: 1 s

sound_period: 3.5 s

sound: when enable_signal

when (sound + sound_period) and enable_signal

until sound_duration since begin sound

until not enable_signal

output 1: sound

When multiple “when” or “until” conditions are specified, they are processed in parallel, i.e. they are essentially combined as an “or” expression.

The delayed expression “sound + sound_period” would automatically restart the sound after each period. It is however gated by the enable signal.

Expression “since begin sound” is needed because “since sound” would count time from the end of the sound. Expression “until sound_duration since begin sound” is preferred to “until sound + sound_duration” because it avoids a possible delayed effect after a new enable signal.

Here, expression “until not enable_signal” amounts to “until end enable_signal”.

4. Lists and loops

Since Whand is declarative and not procedural, it does not provide any simple way to set up processing loops. It is not a serious drawback. First, the general loop that controls all objects is implicit in Whand. Then, loops in a procedural language are mostly needed to repeat the same processing on multiple items belonging to the same collection (an array, a list, a set, etc.). They may also serve to repeat multiple processing steps on the same item or group of items, but this essentially concerns computing applications for which Whand is not really appropriate.

Whand understands lists and provides tools to work with lists without requiring loops. Consider for instance the problem of keeping track of depleted items in a stock. The stock will be represented as a list of item names, and the value associated with each item will be the amount of that item remaining (in percent). We want to extract a list of items that are depleted, i.e. with less than 40% remaining.

Whand allows the following instruction, which relies on internally defined function “pick”:

depleted: stock pick (stock<40)

This formula returns a list of items that have a value lower than 40. Note that it is the value and not the name of each item that is compared to 40.

The equivalent formula in a procedural language would be something like:

depleted = pick_if_less(stock, 40)

where function “pick_if_less” could be defined as follows:

Function pick_if_less (stock as list, n as number):

set selection to empty list

for each element in stock do: {

if value(element) < n:

append element to selection

}

return selection

This function uses a “for” loop to cycle through all elements in the list.

The power of list processing in Whand is not just that a number of functions such as “pick”, “count” or “ramp” are predefined. It is also the fact that a list is considered as a single object when submitted to elementary operations. This property, which I call distributivity, is illustrated in the expression “stock<40” used above:

Operator “<” would normally only compare numbers (or delays) to return a logical value, true or false. It would not be expected to work between a list and a number. But because of distributivity, the comparison is actually performed between each value in list stock and the number 40, so “stock<40” returns a list of logical values.

Fortunately, such a logical list is exactly what the “pick” operator expects as a second argument. Thus, “stock pick (stock<40)” will use the list of logical values to return the list of element in stock that fulfill the condition “element<40”. A true value will let the element be included; a false value will exclude it.

Distributivity is a general property of operators in Whand, and it greatly simplifies the processing of list without requiring any loop structure. Moreover, all this is done while preserving the intuitive feel of the Whand code.

More about Whand

There is much more to Whand (various operators, predefined functions, user-defined functions…) but a full description and comparison to procedural languages would exceed the scope of this presentation. A large number of languages and applications do include at least some parallelism and event driven programming. Python, for instance, provides a parallel visual interface library called tkinter.

Whand, however, is unique in that it completely forgoes sequential programming and employs the same “when” syntax throughout for better accessibility.

A program in Whand only consists of the definitions of various objects that may refer to one another. The general form of a definition is:

object_name: when condition: value

The transition of condition from false to true is what triggers the attribution of value to the object. The value persists even when condition returns to false.

Several “when” clauses may be included for the same object. Some simplifications of the syntax are allowed for clarity. Value “true” may be omitted. “until” allows “false” to be omitted. A “when condition” may be omitted if the relationship between the object and the value is permanent.

Five types of values are allowed: a logical event (true/false), a number (integer or floating-point), a delay (number followed by unit), a state (a name), and a list (a series of object names separated by commas). Operations between objects of various types are allowed when they make sense, e.g. “delay1/delay2” is a number but “delay1*delay2” is not allowed.

One advantage in Whand is that an object’s behavior is entirely determined by its definition. This definition is concentrated in a unique location, which facilitates debugging. On the other hand, this limits the ways in which one can separately manipulate elements in a list. To accomplish this, each element must have its own explicit definition.

Here is an example of using separate element definitions for a three-way light switch:

switch(front): pin 1 # define one element (input)

switch(middle): pin 2 # define another element

switch(back): pin 3 # define yet another element

light_on: nb_on(switch) is in (1,3) # switch list contains three elements

nb_on(buttons’): count pick(buttons’) # user-defined function (for clarity)

output(9): light_on # link variable to hardware

Devising a procedural version is left to the reader. The user-defined function is optional and has been included here to clarify the meaning of the “count pick” expression. “pick(buttons’)” extracts from list “buttons’” the logical elements that are true (on). The prime ‘ after “buttons” signals a function argument. The light is off when 0 or 2 switches are on. “front”, “middle” and “back” are just labels (state values). State values need not be explicitly defined.

In summary, Whand simplifies coding by making implicit and automatic a number of functions that need to be explicit in a procedural language. Variables/objects not only have values or states but also transitions that can be directly exploited in conditions (transitions can also be referred to explicitly using function “begin”, “end“, or “change”). The permanent loop that monitors inputs in procedural languages is here entirely implicit. Delays are seamlessly integrated with events and do not require reference to a clock. Lists are not browsed using loops but instead are treated globally using distributivity and list functions. The result is a code that is compact yet readable and intuitive.

5. Limitations of Whand

Whand primarily aims at controlling appliances, with states that are “on” or “off”. It is comfortable with timing, and can perform simple computations, but it is not appropriate for complex algorithms.

In particular, a disadvantage of parallelism is that objects and expressions tend to be updated and evaluated in any order. Some care must be taken not to test a value or expression just when it undergoes a transition, but only after a brief delay (called “epsilon”).

Moreover, there are instances where computation must proceed according to a sequential order. This can be done in Whand but usually at the cost of more complicated expressions.

For instance, assume we want to count items of different types in a list, i.e. from list (B,A,C,A,D,D,B,D,A,D) extract item types (A,B,C,D) and counts (3,2,1,4).

The procedural version in pseudocode is relatively straightforward:

Function get_types (L as list): # extract item types A,B,C,D

set types to empty list

for each element in L do: {

if element is not in types:

append element to types

}

sort types

return types

Function count_by_type (L as list):

set types to get_types(L) # get item types

for each element in types do: {

set counts(element) to 0 # initialize counters

}

for each element in L do: { # loop through L

increment counts(element)

}

return types, counts

The same function in Whand uses a rather contrived combination of list functions and distributivity:

L: B,A,C,A,D,D,B,D,A,D

types: sort(L pick ((L find L) = ramp(count L))) # extract unique items A,B,D,C

counts: steps(((sort L find types)-1) add count L)(1+ramp count types) # get all counts

This is a quite obscure code, which challenges all but the boldest reader. Hints: “sort” sorts a list in ascending order; “L find x” lists the first position of item x in list L; “steps” computes the differences between pairs of consecutive values in a list. “add” appends a new element to a list; “(1+ramp count types)” indexes list “steps(…)” with list (2,3,4,5) to extract a sublist.

Fortunately, it is also possible to mimic a loop structure in Whand. Yet, the code is not equivalent to the procedural version. Whand still requires some list functions and distributivity (distr.):

L: B,A,C,A,D,D,B,D,A,D

# Extract item types A,B,C,D

list_indices: ramp(count L) # 1,2,3,…,10

clock1: any(start+list_indices*2*epsilon) # ten events spaced by 2*epsilon (distr.)

L_index: count clock1 # index incremented on each event

types: when start: empty # initialize types list as empty

when clock1+epsilon and not(L(L_index) is in types): old add L(L_index)

when (L_index=count L)+2*epsilon: sort(old) # finally sort types list

# Count items by type

type_indices: ramp(count types) # 1,2,3,4

offset: 2*(count L + 2)*epsilon # delay to complete the first loop

clock2: any(start+offset+type_indices*2*epsilon) # four events spaced by 2*epsilon (distr.)

type_index: count clock2 # index incremented on each event

counts: when start: empty # initialize counts list as empty

when clock2+epsilon: old add count(L pick (L=types(type_index))) # (distr.)

Note that the timing of each operation must be specified by the clocks, and clock2 must start after clock1 has terminated (thus “offset”). Unit time “epsilon” allows sequencing, but is short enough that the code runs as fast as possible. Function “old” is required to designate the current object (here types or counts) before it changes value.

We touch here the limits of the language.

6. Implementation

Whand is open source and available for download or inspection on:

https://github.com/MarchandAlain/Whand

Full documentation about the language is provided on the site. Whand runs under Python on either Windows® or Linux platforms. Drivers for Raspberry Pi and ASinterface are provided. It is also possible to run Whand in simulation mode without external hardware. A control panel displays the state of selected variables throughout the test.

The software is currently used in the lab to control rodent conditioning cages from Imetronic® (Pessac, France). Several completely independent programs can be run in parallel, with all events recorded as text files.

At present, Whand focuses on controlling on and off events on external hardware. It does not allow the manipulation of documents except for reading or writing text files. However, an interface with Python procedures is provided, so that its functions may be extended to cover further needs.

My hope is that a growing community of users will develop around the language and that developers may become interested in improving and extending it. Any feedback, comment or suggestion about Whand is really welcome.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store