Luban Composition Tutorial

 

1           Composition 101

Luban structure composition is another way to define the body of a structure versus process. Luban composition is simply a list of sub-components. The sub-component in a composition is called component cell. Each component cell has a name and a definition. The definition of the component cell specifies the content of the component cell and its dependencies with other component cells. When the property of the composition structure is set, Luban interpreter figures out which component cell need to be evaluated according to the dependencies. The below example illustrate a basic composition:

 

            namespace demo;

 

            struct simplecomp ( output oneoone; )

            as composition

            {

                        A1: 100;

                        A2: 1;

                        A3: A1+A2;

                        output.oneoone: A3;

            }

 

The above composition computes 100+1 in a way like spreadsheet. There are three component cells in above structure, A1, A2 and A3. A1 contains a number 100, and A2 is 1. A3 is an expression “A1+A2”. The last line links the result of A3 cell with output property “oneoone”. Then we use the below Luban script to call this structure:

 

            result = demo::simplecomp(=).oneoone; // result = 101

 

To show the difference between composition and process, we change the structure “simplecomp” to below:

 

            namespace demo;

 

            struct simplecomp ( output oneoone; )

            as composition

            {

                        output.oneoone: A2;

                        A3: A1+A2;

                        A1: 100;

                        A2:1;

            }

 

The structure “simplecomp” behaves exactly the same as before. In Luban composition, the order of listing of the component cells does not matter, as far as all the cells and their dependencies remain the same, unlike the process structure for which the sequential order of  code is essential.

 

2           How to Define Component Cells

There are two kinds of component cells that can be used in a Luban composition, ad hoc cell and typed cell. We discuss these two kinds of cells in this section.

 

2.1         Ad hoc Cell

Ad hoc cell contents are very much like what people normally put into spreadsheet cells. They can be constants and expressions. Or it can also be Luban compound statement that may contain arbitrary Luban process code, as shown below:

 

            namespace demo;

 

            struct compadhoc

            (

                        input    in1, in2;

                        output out;

            )

            as composition

            {

                        A1: input.in1 + 1;

                        A2: input.in2 + 1;

                        A3: { std::console().writeline(“run A3”); A3=A1*A2; }

                        A4: { std::console().writeline(“run A4”); A4=A2*A2; }

                        output.out: A3+A4;

            }

 

The above composition uses only ad hoc cells. The cell A1 and A2 are simple expressions. As expected, Luban evaluates the expression and save the result in the cell.

A3 and A4 contain Luban script code. When data updating event reaches these two cells, Luban executes the script code inside the cells. Unlike an expression cell that always results in a new value, a script cell does not update the cell unless the script code explicitly assign new value to the cell itself, as cell A3 and A4 do in above code. And the data updating event will not propagate downstream if the cell is not updated.

The last line in above example links the result of expression “A3+A4” to the output property “out”. The output property linking cell is one special kind of cell that can only contains single expression, no script code is allowed.

 

2.2         Typed Cell

Typed cell is to enable user to directly put pre-defined structure component into composition, like below example:

 

            namespace demo;

           

            struct TwoAdder

            (

                        input op1, op2;

                        output result;

            )

            as process

            {

                        output.result = input.op1+input.op2;

            }

 

            struct FourAdder

            (

                        input op1,op2,op3,op4;

                        output result;

            )

            as composition

            {
                        adder1:  demo::TwoAdder(op1=input.op1, op2=input.op2);

                        adder2: demo::TwoAdder(op1=input.op3, op2=input.op4);

                        adder3: demo::TwoAdder(op1=adder1.result, op2=adder2.result);

                        output.result: adder3.result;

            }

 

The above example illustrates how to compose an adder that can add up four numbers from adders that adds two numbers. You can see inside the “FourAdder” composition there are three “TwoAdder” components inside, two of them linking to input properties while the third one adds the result of first two and send result to output property.

The major difference between ad hoc cell and typed cell is that the typed cell itself does not contain readable value. The other cells can only read the output property of the typed cell while reference of the cell itself will result in error.

 

3           Dependency Specification Rules

Luban composition gives user the freedom to put expression, script and predefined structure into component cells, while Luban figures out the dependencies automatically for you. Yet there are still rules for dependency specification to make sure the composition is semantically clear and unambiguous.

 

3.1         You Can Only Change Yourself, But Do Read Your Surrounding

In a composition, a cell can not modify other cells, it can only read from them. The operations considered as modification include: assignment, member function call and property setting. So if there is such operation upon other cells in a cell definition, Luban will report error. This is to make sure the data flows one way as commonly expected.

A cell can change itself by directly assign value to itself. Though a cell can not read back its own previous value. So calling member function on itself is also prohibited because it involves reading previous value back. The purpose of this rule is simply to avoid forming a self-reference cycle, meaning a cell read itself and update itself making an infinite dependency loop.

 

3.2         No Tail Chasing

Luban does not allow cyclic dependencies among synchronized cells in a composition. The details of synchronized cells can be found in later chapter. So far in all the sample compositions we have seen, all the cells are synchronized.

 

4           Asynchronous Cell

All the cells we talked about above are synchronous cells, meaning they will be evaluated in a single thread one by one according to their dependencies. There are also two kinds of asynchronous cells. An predefined asynchronous structure, when put in composition it becomes an asynchronous cell. And you can also define ad hoc asynchronous cells. The asynchronous cell works in composition like a “live” unit that runs in its own thread generates updating events to its downstream while taking updates from upstream, both in queue. You can view a composition with asynchronous cells as a bunch of “live” cell islands with synchronous cells linked as bridges among them to propagate data updating events.

 

4.1         Typed Asynchronous Cell

 In below example, we code one asynchronous structure to listen to network message. Then we use the structure in a composition.

 

            namespace demo;

 

            asynch struct messenger

            (

                        output msg;

            )

            as process

            {

                        socket = net::socketmsgserver”, 6500);

                        while ( true )

                        {

obj = socket.readobj();

if ( obj == “good bye” ) break;

output.msg = obj;

                        }

            }

 

            struct listenNprint

            ()

            as composition

            {

                        MSG:  ::messenger();

                        PRINTER: std::println(obj = MSG.msg );

            }

 

Above code creates an asynchronous structure to listen to the port 6500 on server “msgserver”, and set the output “msg” whenever object arrives from the server. The “messenger” structure will only stops when it hears “good bye” from the server. The next composition uses this structure as its component and wire it together with a structure that prints message on console. We can run the composition structure using the below script:

 

            demo::listenNprint(=);

 

When the composition “listenNprint” runs, it will listen to the message on network print what it hear on the console until the server says “good bye” to it.

 

4.2         Ad Hoc Asynchronous Cell

You can put ad hoc asynchronous cell into a composition by simple declare the ad hoc process cell as “asynch”. The code inside the cell will follow the asynchronous structure semantics automatically. We can code the “listenNprint” structure using ad hoc asynchronous cell as below:

 

            namesapce demo;

           

            struct listenNprint

            ()

            as composition

            {

                        asynch MSG:  { socket = net::socketmsgserver”, 6500);

                                                while ( true )

                                                    {

obj = socket.readobj();

if ( obj == “good bye” ) break;

MSG = obj;

                                                    }

                                                }

                        PRINTER: std::println(obj = MSG );

            }

 

You may notice that in the ad hoc cell, there is no output property to set except itself. And its fellow component cells can only refer the value of itself instead of the output property.

 

4.3         Asynchronous Component and Cycle

As mentioned in previous chapter, cycle consisted of only synchronous cells is not allowed. In another word, if the cycle has one or more asynchronous component inside, it is OK for Luban. Let’s look at the below example:

 

            namesapce demo;

 

            asynch struct player

            (

                        input receive;

                        output send;

            )

            {

output.send = 1; // send a ball

while ( true )

{

                                    ball = input.receive;

                                    std::println(obj=ball);

                                    if ( ball == 1000 ) break;

                                    ball++;

                                    output.send = ball;

                        }

            }

 

            struct pingpong

            ()

            as composition

            {

                        P1:  ::player(receive = P2.send );

                        P2: ::player(receive = P1.send );

            }

 

It is obvious that the composition structure “pingpong” has a cycle involving cell P1 and P2. Yet Luban will happily take it and run through because P1 and P2 are asynchronous component, so it does not violate Luban’s no-cycle rule.

If you run “pingping” structure, it will print out number 1 to 1000, and print each number twice in a row.

 

5           Inheritance and Composition

Composition can be used together with Luban interface inheritance to create new structure based on old one, like below example shows:

 

            namespace demo;

 

struct AdderPlus implements TwoAdder

(

            output diff;

)

as composition

{

            output: demo::TwoAdder(input=demo::TwoAdder.input);

            output.diff: input.op2 – input.op1;

}

 

In above example, the structure “AdderPlus” takes the interface of “TwoAdder”. It also put one instance of “TwoAdder” into its composition and wired the right portion of its input to the “TwoAdder” and wired the output of “TwoAdder” directly to its output. So “AdderPlus” basically get interface and implementation of “TwoAdder” in this way. It also adds one extra output of its own, which is the difference of its two input properties. The first line in the composition can only be used with structure inheriting interface from other structure. Luban will do type checking to make sure the wiring work with inheritance. There could be ambiguity in the case of multiple inheritance. The basic rule to handle ambiguity is to explicitly specify the wiring of the output property on which the ambiguity occurs.

 

6           Composition VS Process

Composition does its best when there are significant number of components in a system and the dataflow among the components are complicated. It is difficult to code multi-directional dataflow with process that is basically one dimensional.

Process should be used when the system have sophisticated control logic, meaning if conditions and loops. In practice, it could be the case that small components more likely use process while large components could be more compositional.