Tuesday 4 June 2019

Tridash: Why yet another programming language?

Another Programming Language?

I recently released version 0.4, the first version that is remotely usable, of Tridash, a programming language I've been working on.

You're probably wondering, with all the existing programming languages available supporting a variety of programming paradigms ranging from low-level imperative programming to logic programming, why on earth would I create a new programming language. What problem do I aim to solve with this new language that has not been solved already by the hundreds of programming languages that came before it. To tell you the truth I mainly created it because I like writing software and trying out new ideas. Creating a programming language was an interesting and fun exercise in itself. However this a legitimate reason for creating a new language, besides the programming exercise.

I've tried many languages ranging from low-level C, object oriented languages such as C++, C#, and Java, scripting languages such as Python, PHP and JavaScript and languages which implement entirely different programming paradigms such as Common Lisp, Prolog and Haskell. Whilst all languages excel at different points, they are all rather lacking when it comes to creating robust and interactive applications. Specifically they are lacking when it comes to managing the state of the application and the changes in its state triggered by the user interaction or external events such as a data arriving on the network.

State Management Problem

Imperative programming languages allow for raw unrestricted mutable access to the underlying memory, that means you can read and write to virtually any address in the process's memory space. These languages generally provide few tools for managing the state of the application. It is entirely up to the programming to establish "listeners" for each interesting event, such as the user entering text in a text field or the arrival of data on the network, and then manually update the state, by mutating memory directly, of the entirely application. 

As a simple example, consider an application consisting of a text field, in which the user enters his/her name, after which a greeting (with the user's name) is displayed.

We want something similar to the following:


In an imperative programming language you would have to setup a listener for the event which is fired when the user enters text in the field, and then manually update the label below the field, by writing to the memory location in which the text is stored, either directly or by some setText method/function, and then calling a redraw method/function. The actual mechanics may differ, depending on the language and GUI framework, however the point is that it is up to the programmer to update the state of the components, which comprise the application, when the state of a component changes. Besides being both tedious and error-prone, the resulting application is usually inflexible with the actual application logic intertwined with the event listener and state updating boilerplate. As a result, changes in the application logic often require an infrastructural change in the code. The problem is exasperated when concurrency is involved. Suddenly the application logic is buried in layers of thread synchronization logic.

Functional programming languages eschew state completely. In a pure functional programming language, memory does not exist. Instead the entire application is structured as a pure function, i.e. without side effects, of its input, which produces the application's output. In order to build interactive applications using a pure functional programming language, all application events (both user and external) are grouped into an infinite list, referred to as a stream, and the application is structured as a pure function taking the stream as input. The output is a new "application" object containing the updated GUI and any IO actions which are to be performed. Whilst this approach offers a lot of benefits, such as being amenable to parallelisation due to the application being structured as a pure function without side effects and thus without concurrent writes to the same memory location, it requires restructuring the application logic as a function that takes an old application object as input and produces a new application object as output. Effectively you have to think of an interactive application as though it is a non-interactive application, which simply takes an input, produces an output and exits. Furthermore you still have to manually compute a new component from the old component for each component comprising your application. It is also up to you to keep track of which components are affected by the event. The addition of new application components, which are affected by some events, requires a change to the application "main" function.

Neither pure functional programming nor imperative programming allow you to declaratively specify the application state as a set of components which are dependent on the state of other components. Imperative programming grants you too much access to the underlying memory, allowing you to mutate the memory as you see fit. If not done carefully this can result in hard to find bugs, moreover manually updating the state in response to events is tedious and distracts from the application logic. Functional programming, on the end of the spectrum, completely hides the concept of memory forcing you to write your application as a pure function taking the events and old application state as input and producing the new application state. This forces you to think of your application in non-interactive terms, and you still have to manually make sure to compute the new state of each computed affected by the event.

Bindings and Reactive Programming

A binding between two components A and B is an assertion that whenever the state (value) of one component A changes, the state of the other component B is updated to match the state of A. Bindings can also involve more complex functions, e.g. if C is bound to A + B, then whenever the value of either A or B changes, C is set to the result of recomputing A + B. This paradigm is actually most widely used in spreadsheets where the value of a cell is a function of the values of other cells. 

Most GUI toolkits provide some support for simple bindings however they either lack the functionality for specifying bindings involving an arbitrary function of two or more components, or implementing such a binding requires implementing value "converter" or "transformer" interfaces in a lower-level language. This is usually so cumbersome that most programmers prefer to simply drop down to imperative programming.

Functional reactive programming languages are based on the idea of bindings involving arbitrary functions. Admittedly I haven't tried that many FRP languages, however those which I have tried seem to stick more closely to the traditional functional programming paradigm of implementing your application as a pure function. Few allow you to implement an application as a set of distinct nodes with bindings between the nodes.

Enter Tridash

The Tridash language aims to provide a simple and intuitive solution to the State Management Problem, which allows you to think of your application as an interactive application composed of live stateful, meaning they hold a particular value at a given moment in time, components called nodes.

Nodes can correspond to purely computational components or other stateful components such as UI elements, the network or the file system. Bindings between nodes, involving arbitrary functions of other nodes can be declared, using the '->' operator. Examples:
  • a -> b
  • a + b -> c
The first example demonstrates a simple binding in which: when the value of a changes, the value of b is updated to it.

The second example demonstrates a binding involve a function of two nodes. When the value of either a or b changes, c is set to the sum of a and b.

What sets Tridash apart is the free-form manner in which nodes can be declared. There is no main function just a set of declarations. Bindings can be declared between any pair of nodes.

More than just bindings between nodes, Tridash allows you to treat the bindings as nodes in themselves. Binding to a binding allows you to control which bindings are active. 

Example:

cond -> (a -> b)

In this example if cond evaluates to true the binding a -> b is active, whereas if it evaluates to false it is as if the binding does not exist, meaning changes in the value of a will not be propagated to b. This functionality is used to build a unique error handling scheme, in which bindings are deactivate in the case of an error.

In the next major release you'll also be able to establish bindings between a node and its past states.

The hello example presented earlier can be implemented simply using a single binding declaration.

"Hello: " + name-field.value -> greeting.textContent

where name-field.value is a node corresponding to the value of the text field UI node and greeting.textContent corresponds to the content of the UI node in which the greeting is displayed.

Programs as a whole are composed as a collection of a set nodes and bindings between the nodes. Changes in the value of a node, are automatically propagated to the nodes which are dependent on the node's value. This allows you to focus on the core application logic rather than the mechanics of propagating changes in state. The benefit of this is that the values of nodes which are not dependent on each other can automatically be updated concurrently by the underlying framework, without any additional work.

In conclusion the Tridash language presents a new view of your program, as a collection of live inter-connected components, which provides numerous benefits over the traditional view of your program as a linear sequence of instructions or a linear composition of functions.

Status

Tridash is currently at version 0.4 however it is still an alpha product and lacks the necessary features for building real-world applications, such as data-structures and a standard library. Currently only a JavaScript backend is implemented which uses HTML to provide a user interface. Other backends are planned for a future release.

Tutorials demonstrating the basic functionality of the language. Advanced tutorials and a reference manual are in the works.

Features planned for the next major release:
  • Data Structures: Lists, Maps, etc.
  • Macro System
  • Standard library containing math functions and other commonly used functions.
For installation instructions and tutorials head over to the project's wiki:
The name is a portmanteau of the words triangle and dash which are the two symbols comprising the bind operator ->, the main operator in the language. The > symbol somewhat looks like a triangle, right?