An informal explanation of the Ivory programming system

In this section we present an informal explanation of various aspects of the Ivory programming system. The aim here is not to cover everything, rather to explain the general intentions behind it.

A knowledge of functional programming, and Haskell in particular, would be helpful, but is not essential. As an aid, significant differences will be highlighted.

We will begin by taking one of the examples and examining it in detail.

Assume that the Clock application has been launched by dragging the script clock.is over the Ivory system link icon. The effect is that Tick and Tock are alternately shown on the system console every second. How was the system programmed to achieve this?

The principle behind every Ivory application is that its structure and current state are maintained in one or more data stores, with events causing transitions between states - a bit like stepping stones in a river. Thus with a programming requirement, we usually start by modelling structure and state data.

For our clock, from a static point of view, some kind of timer seems to be needed and a flag that can take one of two values. Dynamically, the following periodic actions are required:

In order to implement this, we turn our attention to the system programming language IvoryScript. Although it has different purposes, we will at first concentrate on using it to build data stores. The means to do this is known as an order script and clock.is is a typical one. Order scripts just comprise a sequence of expressions that are evaluated in order.

IvoryScript expressions have familiar syntactic forms (very much in a functional programming style). E.g.


and so on.

Evaluation means the process of simplifying (reducing) expressions as far as possible and (for order scripts only) displaying results.

The Ivory system is strongly typed in that values are divided into different sets, and that they are appropriate for a given context.

integers Int 123
floating point numbers Float and Double 3.14159
enumerations Bool True and False
character strings String "An example of a string"
a mapping between a string and an integer String -> Int lengthString
etc.    

Difference to Haskell

There is a Void type for functions that return no direct value.

The mapping or function type is said to take an argument of one type and return a result of another. Functions form the basis of all substitutions during the reduction process, as different argument values are applied to values of function types.

Where expressions denote values (e.g. 1000 / 2), during the reduction process they are either evaluated before substitution or substituted unevaluated (lazy evaluation); again depending on the context. There are distinct data types to signify this. For example, 1000 / 2 has type Int when substituted evaluated and has type (Exp Int) when it is substituted unevaluated.

(Exp Int) here is referred to as a parameterised type.

Difference to Haskell

Haskell has no explicit expression type.

Let us return to our example clock.is and pull apart a key expression and see what it means:

.timerStore.Timer[
   period:  1000 / 2,
   flag:    False,
   proc:    (\timer::Timer -> {
                show ((if not timer.flag then "Tick" else "Tock") ++ "\n");
                timer.flag := not (timer.flag)})];

There is a small syntactic convenience here, which (after undergoing a minor transformation) is equivalent to something like

Timer [ ... ] (.timerStore)

This expresses two arguments applied to a function. Here, the function Timer is something known as an object constructor. We don't need to know the details yet, except that, when evaluated, it causes a timer object to be created in a data store.

Questions should now spring to mind

The answer to the first is simple, but it does not conform to normal notions of object orientation, so we will state it directly as follows:

An object is an evaluated expression associated with a data store and referred to by reference.

The Ivory system does have classes, inheritance etc which will be discussed further. However, they are entirely based on functions and types and not object based. Type classes were adopted for the Ivory system on the belief that they provide a more natural model. In reassurance to those familiar with object orientation, the effect is often very similar.

Ivory data stores are best considered by analogy to the files and directories in a networked filing system. There is a root store of objects, some of which may be other stores etc and so on: forming a hierarchy. There are also name directories to associate identifiers with particular objects; in general, only a small proportion of objects will be named in this way.

The target data store is determined by the last argument of an object constructor. This is either . or Root or any expression referring to a store in the hierarchy.

For our example, a store for timer objects was created by a similar syntactic form earlier in the script

.timerStore =.ADS [ruleList: !RefList[] ];
transforming to, to be wrtten as '=>'
addVar (Binding #timerStore (ADS [ruleList: !RefList[]] Root) Root)

Again, on evaluation, an ADS (an acronym for Active Data Store) is created in the Root store. Additionally, the returned reference is associated with the identifier timerStore.

We can now turn our attention to the interesting first argument of many typical object constructors (e.g. ADS and Timer). Frequently when modelling an entity, it is convenient to assign it a general classification and distinguish it from others by named properties. Also, it is often that slightly different sets of properties apply to different individual entities. When modelling for the Ivory system, an object constructor name can provide the classification, and the first argument the set of properties in the shape of a list.

Formally, the data type for this argument is:

Exp (List Exp(Property))

Which reads as: an expression denoting a list of expressions denoting values of type Property.

Ivory follows normal conventions for lists in that the elements are comma separated within square brackets.

[1, 2, 3] 
[Red, Green, Blue]
[[1, 2, 3], [4, 5, 6]]     -- a list of lists
[p1: 123, p2: "a string"]  -- a list of bindings (properties)

Each element of a property list has the form:

name: expr

where the type Property is a synonym for

Binding (Exp *)

and Binding is a parameterised type associating a name with a value.

Difference to Haskell

The : symbol in IvoryScript is used for infix binding and not list construction.

Two important Ivory system data types can now be introduced.

  1. Name - for identifiers as values. In the case of :, the IvoryScript syntax specifies the left hand argument as such an identifier. At other times, identifier values are prefixed with the # symbol to distinguish them from other names.

    Aside
    It is perfectly possible to have dynamically named properties. To do this, the binding constructor must be applied directly. E.g.

    Binding (foo x) y
    

    assuming that foo x denotes a value of type Name.
    end of aside

  2. * or Any. An Ivory system is dynamically typed, permitting values where the type can only be determined at the time of execution. Any evaluated value is a member of *.

    For purely pragmatic reasons of current implementation technologies, values of type * may not be passed as arguments to functions; they can only be passed unevaluated or returned as results.

Although it is not directly visible at this level, ADS and Timer are both based on PropertySet, which is designed for efficient implementation of sets of dynamically determined properties.

Core functionality

With most programming environments, it is desirable to build on what has gone before and the Ivory system is no exception to this in providing a repository of built-in modules. So at this point, we might expect some kind of timing functionality to be already available as well as information about it, e.g. Timer module.

Typically a module will attempt to provide as high a level of abstraction as possible. For timer objects, period and proc properties suffice; any operating system dependencies are hidden.

The period property seems fairly obvious, but there is one thing to note here. This concerns binding values, which are defined to be unevaluated. For the case of 1000 / 2 (written like this to make the cycle length clearer), after the first time it is evaluated, it will be destructively updated to 500.

In this case, the property is essentially constant, so there is no problem. But there are cases where what appears to be constant might in fact vary. For these, a modifier variable can be applied to the expression to prevent any update, and the original expression will always be evaluated. E.g.

...
period: variable 1000 / 2;
...

Difference to Haskell

As there is no destructive update, this doesn't apply to Haskell.

The timer module specifies what happens each time the timer elapses. For our example, it is effectively to evaluate:


(\timer::Timer -> {
   show ((if not timer.flag then "Tick" else "Tock") ++ "\n");
   timer.flag := not (timer.flag)}) (.timerStore.timer)

Such an evaluation is termed a spark to signify that it is expected to be short-lived and then die out.

In broad terms, the following notes capture some of the semantics:

Stored higher order data types

Order scripts like clock.is are evaluated to completion in a spark when an Ivory system starts up and then discarded. So what is going on here?

What happens is that when objects are created, expressions may be copied to the target store. This is the reason for not adopting one of the currently available virtual machines. The Ivory system byte code and virtual machine has been specifically designed to allow for sub-expression extraction.

Implementation note: lambda closures are marked as potential copyable are placed in special autonomous segments


home

Last update: 11 October, 2005