The LIDL Interaction Desccription Language
Last updated 5 years ago by crubier .
MIT · Original npm · Tarball · package.json
$ cnpm install lidl 
SYNC missed versions from official npm registry.

Travis Gemnasium Code Climate npm npm


A simple language to describe interactive systems.

Programming languages are nice for computations. However, describing interactions with programming languages is painful. We need a language for interactions. That is what III is for.

Forget about programming, just describe interactions and let III do the rest.

This document contains the following sections:


This tutorial will cover the basic features of the III Language:

Focus on interactions

A III system represent one interaction. An interaction can be composed of several simpler interactions. This means that any III system is a composition of interactions. In fact, in III, pretty much everything is an interaction. Conditionals, assignments, variables... Everything is represented as interactions.


Since we want to describe interactive systems, the only entity that adds value to the description is the interaction. We are interested only in aspects relevant to the interaction of systems. The details of the computations are not relevant for our purpose, so they are defered to other languages.

Simple syntax

Thanks to the over-use of parentheses, the expression grammar of III is really simple:

expression := '(' element* ')' element := expression | identifier identifier := anything without parentheses

Said simply, an expression is a sequence of identifiers and sub-expressions, enclosed in parentheses. Here is an example expression in III:

( when (anEvent) do ( (this) and (that) ) )

But, you ask, how do we know which operator an expression refers to? Well, this is simple: the name of the operator of an expression is the concatenation of its identifiers, with sub-expressions replaced by $. $ stands for "something". For example the expression ((a)+(b)) uses the operator $+$, pronounced "something plus something".

To make a parallel with a more classical way to represent expressions, the above expression would usually be represented as :



Why such an unusual syntax ?

First, the resulting syntax is very general, and similar to natural language, just with a lot of additional parentheses. These parentheses are made invisible when editing the code in an appropriate IDE, which makes reading III as easy as reading natural language.

There is only one syntax for all: All these prefix operators like !a, infix operators like a + b, postfix operators like i++, named operators like sin(x) are represented the same way. This should hopefully make III easier to understand for beginnners.

Finally, this syntax takes the current evolution of programming languages to its logical conclusion. For example, it generalizes the notion of Named function parameters as used in Apple's Swift Programming Language, and in Microsoft's C#. Javascript developpers also use argument maps instead of positional arguments. Here is a table comparing different programming languages:

Language Expression
C, Java CalculateBMI(83,185)
Javascript CalculateBMI({weight:83,height:185})
C#, Swift CalculateBMI(weigth:83,height:185)
III (Simple) (BMI (83)kg (185)cm)
III (Verbose) (Body Mass Index of someone who weights (83) kg and is (185) cm high)

Note how III's syntax allows expressions to be much clearer on their semantics. For example, expected units of parameters are only expressed in III.

Synchronous execution

III systems are synchronous. This means that interactions are evaluated at discrete points in time, all at once. For example, the expression:

( when (anEvent) do ( (output) = (5) ) )

Will be evaluated at discrete points in time, called steps. Every expression of this composed expression will be evaluated at every step. For instance, here is a simplified execution, which does not actually comply to the actual semantics of the language:

Step anEvent output
0 no ~
1 yes 5
2 no ~
3 yes 5


Synchronous execution avoids spaghetti behaviours. The state of the system is explicitly defined for each execution step. This makes reasoning about code much simpler.

Synchronous execution also makes verification much easier.

Interaction activation

Behind the scenes, data is represented as a couples < activation, value >, where:

  • activation is a boolean.
  • value has a type. We will explain values and types later.

This means that every expression, every sub-expression is actually a couple _< activation, value > _. Even the assignment, $=$, is a couple < activation, value>.

An interaction's activation represents the fact that the interaction exists and is active at a point in time, or not. For example, if an interaction represents an event, then it will only be activated when the event happens. As another example, the assignment interaction $=$ is only effective when its activation is true. If an interaction's value is not defined anywhere, then its activation is false.

If an interaction activation is false, we write its value as ~. Here is a table showing a few examples:

Couple Notation
$ \langle \top, 0 \rangle$ 0
$ \langle \top, 1 \rangle $ 1
$ \langle \top, \top \rangle $ true
$ \langle \top, "text" \rangle $ "text"
$ \langle \bot, 0 \rangle $ ~
$ \langle \bot, 3 \rangle $ ~
$ \langle \bot, "text" \rangle $ ~
$ \langle \bot, \top \rangle $ ~


In previous approaches, and in some other languages, a difference is made between events and flows, which are considered as two different first-class entities.

In these approaches, events represent data defined at discrete points in time, while flows represent data defined on continuous time intervals. But when we think about it, the only difference between an event and a flow lies in the domain of the time → value function. For events this set is discrete. For flows this set is continuous.

The logical conclusion of this remark is that the merger of these two concepts needs to include the indicator function of the domain of the time → value function. This indicator function is the activation, while the time → value function is the value.

Emissions and Receptions

Interactions can represent reception of data, they are then called reception. Receptions are really similar to functions in other programming languages. Like functions, they take data in, compute and return a value, without side effect. Receptions are the things which are on the right-hand side of assignments.

Interactions can also represent emission of data, they are then called emission. Emissions are the opposite of receptions. As a consequence, experienced coders struggle to understand emissions at first. The data flow in emissions is the exact opposite of the data flow in receptions. Instead of returning a value, receptions are given a value; instead of being given arguments values, emissions return values for their arguments. Emissions are the things which are on the left-hand side of assignments.

Some interactions represent both reception and emission are just called interaction. They can emit and receive data. Typically, only state variables are interactions. Inputs of the system are receptions and outputs are emissions. But emissions are not necessarily outputs, and receptions are not necessarily inputs. They can be internal.

As a first example, look at the following interaction:

( when (anEvent) : ( (theOutput) = (5) ) )

In this interaction, anEvent is a reception, while theOutput is an emission. But that is not all ! Remember, everything is an interaction ! Here is the complete table of interactions of this example:

Interaction Type Kind Operator
(anEvent) void reception anEvent
(5) number reception 5
(theOutput) number emission theOutput
((theOutput) = (5)) void emission $=$
( when (anEvent) : ( (theOutput) = (5) ) ) void emission when$:$


Again, this language feature is taking evolutions of languages to its natural conclusion. Here is a table presenting the evolution of how different programming languages treat assignments:

Language Expression Remark
Assembly cpy res, a; add res, b; Expressions cannot be nested at all
C, Java res = a + ( b * c ); Arbitrary expressions on the right hand side of assignment, only variable names on the left hand side
JS ES6 { x, y } = f(a,(b+c)); Arbitrary expressions on the right hand side of assignment, restricted subset of expressions on the left hand side
III ( ( (x) or (y) ) = ( (a) + (b) ) ) Arbitrary expressions on both sides of the assignment

This shows that the emission / reception paradigm is a generalisation of current evolutions of programming languages, such as the destructuring assignment.

Base data types

There are few different base types:

Type Description Example literals
void Bottom type, carries no data except activation information active, ~
boolean When active it can be either true or false true,false, ~
number Can be an integer or a float 0,1.,13.37,-42, ~
text Any sequence of unicode characters "foo", "this is a text", ~


Do we need more ? The user can always define custom enumerated types, so we should be ok like that. Booleans are not even necessary.

Complex data types

There are different constructions for complex types:

Type Description Example type
[ <type1>, <type2> ] Tuples are combinations of a fixed number of different types in a certain order [boolean,number,number], [number,text]
{ name1:<type1>, name2:<type2> } Objects are tuples whose elements are identified by name instead of position {x:number,y:number},{id:text,value:number}

The following complex data types may one day be implemented in III:

Type Description Example type
[ <type1>... ] (Not implemented) Arrays are combinations of variable number of identical types [number...],[text...]
{ <typekey>:<typevalue>... } (Not implemented) Maps are arrays whose elements are identified by name instead of position {text:number...},{number:text...}
[ <type1> or <type2> ] (Not implemented) Unions are tuples with only one value active at a time [ text or number ], [ {x:number, y:number} or number ]
[ <type1> → <type2> ] (Not implemented) Pure functions with domain type1, codomain type2, and no side effects [number → text], [void → number]


For each Tuple type, a set of emissions and receptions are defined. For example, the type:

data myType : [ <type1>, <type2> ]

Will generate the following emissions and receptions, lazily at compile-time:

  • Decomposing receptions (example use: x = a[1])
  • <type1> reception ( (myType reception x) [1] )
  • <type2> reception ( (myType reception y) [2] )
  • Decomposing emissions (example use: a[1] = x)
  • <type1> emission ( (myType emission x) [1] )
  • <type2> emission ( (myType emission y) [2] )
  • Composing receptions : (example use: a = [x,y])
  • myType reception ( [ (<type1> reception x) , (<type2> reception y) ] )
  • Composing emissions : (example use: [x,y] = a)
  • myType emission ( [ (<type1> emission x) , (<type2> emission y) ] )

These definitions allow to write such things:

[ a, [b, c] ] = [ 3, [4, 5] ]

Which is equivalent to:

a = 3; b = 4; c = 5;


Similarly, objects are interactions, so they can be received and emitted

{ weight:a, height:b } = { weight:83, height:184 }

is equivalent to:

a = 83; b = 184;

Any composition of complex types is an interaction, so this is possible:

[{name:nameOfTheHeaviest,weight:maxWeight},...] = ( sort by weight ([{name:"toto",weight:50},{name:"titi",weight:75}]) )

is equivalent to:

nameOfTheHeaviest="titi"; maxWeight=75;


Interaction involve exchange of complex data. Complex data should be part of the language.

In combination with other languages features, complex data types enable very expressive code.

Data type vs Interface

Types :

  • bottom type : activation = {} = () = []

    • enumeration : enum(literal1,literal2,literal3)
    • boolean : boolean
    • number : number
    • text : text
    • tuples : (<type1>,<type2>)
    • structures : {field1:<type1>,field2:<type2>}
    • arrays : [<type1>]
    • unions : |<type1>,<type2>|
    • top type : any = |<firsttype>,<secondtype>,...,<lasttype>|

    The classical notion of data type encompass two different notions :

    • Data type of a function declaration (e.g. float sin(float x);) : We call it interface because it is the specification of the type of data that can be received or sent by an interaction. It does not specify the type of any actual data, but it merely gives the shape of the channel the data flows through. Channel types are not specified in most weakly typed languages. They are specified explicitely in III.
    • Data type of a variable instantiation (e.g. int x = 2;) : We call it data type because it is the specification of the type of the actual data. Data types are often specified as var in weakly typed languages, and redundantly declared in strongly typed language. They are specified implicitely and infered automatically in III.

    In III, data types are specified implicitely, through instantiation, while channel types are specified explicitely.

    A data type is a simple type expression.

    A channel type is a type expression, with additional information about the flow direction using in and out keywordds. Channel type is a type expression tree where the only path going from the root to any leaf crosses one and only one "in" or "out".


    Basically, don't fix the direction of dataflow ! Restrict it to AST edges to enable compositional verification, but allow it to go in both direction at the same time instead of only down (classical languages) or only up or down (iii with simple types)

    Base Interactions semantics



    void emission ( (<type1> emission a) = (<type1> reception b) )

    Example of use

    ( (a) = (b) )


    When the assignment ((a)=(b)) is active, the emission (a) on the left is given the value returned by the reception (b) on the right.

    Truth table

((a)=(b)) (b) (a)
active value value
active ~ ~
~ value ~
~ ~ ~

Example chronogram

Step ((a)=(b)) (b) (a)
0 ~ 4 ~
1 ~ 5 ~
2 active 6 6
3 active 7 7
4 active ~ ~
5 ~ 9 ~



<type1> emission ( all (<type1> emission a) (<type1> emission b) )

Example of use

( all (a) (b) )


The value given to (all(a)(b)) is given to both (a) and (b)

Truth table

(all(a)(b)) (b) (a)
value value value
~ ~ ~

Example chronogram

Step (all(a)(b)) (a) (b)
0 ~ ~ ~
1 ~ ~ ~
2 6 6 6
3 7 7 7
4 ~ ~ ~
5 8 8 8



<type1> emission ( either (<type1> emission a) (<type1> emission b) )

Example of use

( either (a) (b) )


The value given to (either(a)(b)) is given to either (a) or (b)

Truth table

Non deterministically choosen between the two following tables:

(either(a)(b)) (b) (a)
value value ~
~ ~ ~
(either(a)(b)) (b) (a)
value ~ value
~ ~ ~

Example chronogram

Step (either(a)(b)) (a) (b)
0 ~ ~ ~
1 5 5 ~
2 6 ~ 6
3 7 ~ 7
4 ~ ~ ~
5 8 8 ~



void emission ( when (void reception a) : (void emission b) )

Example of use

( when (a) : (b) )


When (when(a):(b)) is active and (a) returns the active value then (b) is given the active value. Note how this is similar to the assignment ((b)=(a)) with void values.

Truth table

(when(a):(b)) (a) (b)
active active active
active ~ ~
~ active ~
~ ~ ~

Example chronogram

Step (when(a):(b)) (a) (b)
0 ~ active ~
1 ~ active ~
2 active active active
3 active active active
4 active ~ ~
5 ~ active ~



void emission ( always : (void emission b) )

Example of use

( always : (a) )


(always:(a)) forces (a) to be active, even when (always:(a)) is given the inactive value ~.

Truth table

| (always:(a)) | (a) | |:----------:|:------:|::| | active | active | | | ~ | active | |

Example chronogram

Step (always(a)) (a)
0 ~ active
1 ~ active
2 active active
3 active active



boolean reception ( active ( <type1> reception a ) )

Example of use

( active (a) )


When (a) is active, (active(a)) returns true, and when (a) is not active, (active(a)) returns false. (active(a)) itself is always active, even when (a) is not.

Truth table

(a) (active(a))
value true
~ false

Example chronogram

Step (a) (active(a))
0 ~ false
1 3 true
2 4 true
3 ~ false
4 5 true
5 ~ false



<type1> reception ( previous ( <type1> reception a ) )

Example of use

( previous (a) )


Go back in time by one step. (previous(a)) returns what (a) returned on the previous execution step.

Example chronogram

Step (a) (previous(a))
0 ~ ~
1 5 ~
2 6 5
3 ~ 6
4 8 ~
5 ~ 8



void reception ( init )

Example of use

( init )


Active only on the first step, inactive the rest of the time.

Example chronogram

Step (init)
0 active
1 ~
2 ~
3 ~
4 ~
5 ~



<type1> reception ( ( <type1> reception a ) default ( <type1> reception b ) )

Example of use

( (a) default (b) )


If (a) is active, ((a)default(b)) returns (a), else it returns (b).

Truth table

(a) (b) ((a)default(b))
value a value b value a
value a ~ value a
~ value b value b
~ ~ ~

Example chronogram

Step (a) (b) ((a)default(b))
0 ~ 0 0
1 ~ ~ ~
2 3 ~ 3
3 4 5 4
5 ~ 2 2



void emission ( flow ( <type1> interaction a ) change ( <type1> reception b ) )

Example of use

( flow (a) change (b) )


Flow (a) from time step to time step, but give it value (b) when (b) is active.

Equivalent to

( (a) = ( (b) default (previous(a)) ) )

Example chronogram

Step (flow(a)change(b)) (b) (a)
0 ~ ~ ~
1 active 5 5
2 active ~ 5
3 active ~ 5
4 active 3 3
5 active ~ 3
6 active ~ 3
7 ~ ~ ~

Advanced Interactions semantics



<type1> reception ( apply ( [<type2> → <type1>] reception func ) on ( <type2> reception data ) )

Example of use

( apply (func) on (a) )


Returns the result of the application of a function (func) to data (a)

Associated chronogram

Step (func) (data) (apply(func)on(a))
0 ~ 0 ~
1 sin 0 0.
2 sin 2 0.909
3 sin ~ ~
4 ~ 3 ~
5 cos 4 -0.653
6 cos 5 0.283
7 ~ 6 ~


The speed controller application :

(speedcontroller (alarm:boolean out) (displayedspeed:number out) (targetspeed: number out) (actualspeed:number in) (increment: activation in) (decrement: activation out) ): activation in : (all ((displayedspeed) = (actualspeed)), ((alarm)=((actualspeed)>(targetspeed))), ((targetspeed)=(incdec behavior(increment)(decrement)(actualspeed)(5)(30)(150))) ) )

Increment Decrement behaviour :

(incdec behavior (inc: activation in) (dec:activation in) (actual:number in) (step:number in) (min:number in) (max:number in) ):
number out:
    + (step) * (1 if (inc) is active)
    - (step) * (1 if (dec) is active)
    to range (min) (max)

    Just a small utility function :

    (1 if (theSignal:<type> in) is active):
    number out :
    (if (active(a)) then (1) else (0))

    Ensure a value is in a range  :

    (restrict (value:number in) to range (min:number in) (max:number in)):
    number out:
    ( if ((value)>(max))
    then (max)
    else ( if ((value)<(min))
    then (min)
    else (value)


    ## Examples

    This piece of III code will be the running example of this document. It is an implementation of the TodoMVC example used to benchmark UI frameworks at [](


    html data: htmlelement

    htmlelement data:

    htmlattribute data:

    htmlevent data:

    htmlelement reception (<(text reception name)([htmlattribute] reception attr)>()</>)

    html reception (buttonDown) : "image of a button down"
    html reception (buttonUp) : "image of a button up"

    void emission (toggle button (html emission scene) (boolean data model) (void reception click)) :

    void emission (simple button (html emission scene) (void reception click)) :


    mode data:

    todo data:

    void emission (todoItem
    (todo data model)
    (html emission scene)
    (void reception changeCompletion)
    (text reception changeName)
    (void reception remove)
    ) :

    (simple button (removeButton) (clickRemoveButton))
    (toggle button (completedButton) (model.completed))

    (scene=(<(div) ()> () </>));
    (when(changeCompletion):(negate ((theModel).completed)))
    (when(remove):(negate ((theModel).completed)))


    html reception (todoApp
    ([todo...] interaction model)

    void emission (main (html emission scene) (event reception event)) :

    ((scene) = (todoApp));

    Ok test :

    // Signature are :<argument type>:<this type>:<local type>:
    // Syntax interactionName:<argument type>:<this type>:<local type>:behavior

    //WIMP component : signature is :<argtype>:{scene:out scene,event:in event}:<localtype>:

    {step:in number,range:in range}: // argument
    {scene:out scene, event:in event, value:out number}: //this
    {inc: event, incbutton:buton,decbutton:button }: //local
    all [
    flow  {variable:this.value,
    switch: this.value + step * 1IfActive this.event.increment - step * 1IfActive this.event.increment)
    affect (this.scene,

    {scene:out scene, event:in event,clicked:out void}:
    all [
    this.scene = [rectangle(...),text(...)];
    this.clicked = if {condition:this.event.type=="click",then:active,else:inactive};

    // Util

    in <type1>:

    out number:
    when {active:argument,then:affect(this,1),else:affect(this,0)}

    //Behaviors : signature is  :<argtype>:in activation:<localtype>:

    {variable:out <type>,switch:in <type>}:
    in activation:
    when {active:this,
    then:when{ active:switch,
    else:affect(argument.flow,previous argument.flow)

  [out activation]: //argument
  in activation: //this
  {}: //local

  {active:in activation, then:out activation:toVoid,dont:out activation:toVoid}:
  in activation:

  toVoid:void:in activation:void:;

  incrementDecrement (

  [in todo]:
  {scene:out scene,event:in event}:


  in activation (todoWIMP (in todo)):
  {buttonCompleted: toggleButton,nameLabel: label,buttonDelete: button}:




  todo Data :

  event Data :
  addTodo {name:text},
  updateTodo {key:text,name:text}
  completeTodo {key:uuid},
  removeTodo {key:uuid}
  removeComplete {}

  (todoApp) :

  {html:html out, action:action out, even:event in, todoList:todoList in} (todoListDisplay) :
  [todoDisplay] : (map (argument.todoList)          )
  {html : }

  (todoDisplay) :
  {html:html out, action:action out, even:event in, todo:todo in} : -->

  ## Project setup

  install with `npm install`

  make with `gulp`

  - sources in `src/`
  - tests using mocha in `test/`
  - precompiled external libraries in `lib`
  - compiled artifacts in `bin`
  - browserified stuff in `dist`

Current Tags

  • 0.1.9                                ...           latest (5 years ago)

4 Versions

  • 0.1.9                                ...           5 years ago
  • 0.1.8                                ...           5 years ago
  • 0.1.7                                ...           5 years ago
  • 0.1.6                                ...           5 years ago
Maintainers (1)
Today 0
This Week 0
This Month 0
Last Day 0
Last Week 0
Last Month 2
Dependencies (3)
Dev Dependencies (24)
Dependents (0)

Copyright 2014 - 2016 © |