Luban Dynamic Type System Tutorial

 

All the objects inside Luban can be separated into two categories: data types and structure types. Structure types are the components coded in Luban. You can create as many kinds of Luban structure types as you wish just like you can code as many functions as you wish in C.

Data types are those built-in and/or imported data types with predefined set of member functions and applicable operators. Map, set, vector, double, int, bool, char and string are the examples of built-in data types, while std::file, net::socket are examples of imported C++ data types. The fundamental difference between structure type and data type is that you can not code a new data type with Luban. One design philosophy of Luban is that the basic data types are low level software elements that should be accessible from Luban, but Luban should not be used to make low level elements. Just like bricks are used to build house, instead of bigger bricks. Luban’s belief is there should be a fundamental programming paradigm shift from software element level to component level.

Though structure types and data types are naturally different. They still obey a more or less common set of rules. These rules are to be discussed in this chapter.

 

1.      Name of Data Type and Structure Type

The naming rule of Luban data type and structure type are the same. They all must be a full name with name space. But Luban built-in types are keywords and do not need name space prefix. For example:

 

            string;  // built-in type

            std::file; // standard imported data type

            net::listener;  // imported data type

            net::socket; // imported data type

            std::print; // a structure type

 

The name space part in a type name can be omitted only when referred in a component that resides in the same name space, as below code shows:

 

            namespace std;

 

            struct print

            (

                        input obj;

            )

            as process

            {

                        ::console().write(input.obj);

            }

 

The above code simply wraps the calling of member function of std::console type in a structure named std::print. We can see the referring to std::console in std::print structure does not use the name space part.

 

2.      Construction of Data Type and Structure Type

The syntax of object construction in Luban is type name followed by parenthesis, as shown in the below example:

 

            // print hello world

            console = std::console();

            console.writeline(“Hello, world!”);

 

            // print hello world again

            printer = std::print();

            printer.obj = “Hello, world!\n”;

 

Parameters can be used in the object construction. But the syntax of argument passing is different for data type and structure type. Look at the below example:

 

            // create file as data type

            file = std::file(“helloworld”, ‘w’);

            file.writeline(“Hello, world!”);

 

            // print hello world to console

            printer = std::print(obj = “Hello, world!\n”);

 

To pass parameters into data type construction, you just need to line the parameter values up. To construct a structure with specified property values, you need to pair up the property names with its values. Another big difference for structure construction with specified property values is that if the specified properties include input property, the structure will be evaluated once after construction. Yet the structure construction without any specified property values does not trigger any evaluation.

 

3.      Explicit Type Casting

Type casting in Luban is just an object construction with a single argument. Like below:

 

            int(“100”); // result 100

            string(100); // result “100”

            double(100); // result 100.0

            int(3.1415); // result 3

           

Casting to string type is special because it is universally enabled for all Luban built-in and imported data types. Actually when std::console type is asked to print an object, it will cast the object to a string then print the string.

 

Type casting does not apply to structure type. Since user can dynamically access structure properties. There is no obvious needs to cast one structure object from one type to another.

 

4.      Dynamic Type Checking: isa and typeof Operators

You can dynamically check the type of any object in Luban with isa and typeof operators, as shown in below Luban script:

            d = 3.1416;

            s = std::file(“pie”, ‘w’);

 

            //  check type and decide what to do

            if ( d isa double and s isa std::file )

                        s.writeline(d);

            else

                        s.writeline(“The end of world is coming”);

 

            // do the same thing in a different way

            if ( typeof(d) == double and typeof(s) == std::file )

                        s.writeline(d);

            else

                        s.writeline(“The end of world is coming”);

 

The structure interface inheritance is taken into account for type checking. For example the variable “test” in below Luban code has value true because structure demo::Adder inherits interface from demo::DualOp ( Previous example):

 

            adder = demo::Adder();

            test = adder isa demo::DualOp; // test = true

 

5.      Type Expression

One unique feature of Luban is type expression, which gives more power and flexibility to Luban’s type system. Type expression is part of the Luban expression. The result of type expression can be assigned to variable and passed around just like normal object. The details of type expression can be shown in the following Luban code examples:

 

Type name alone can be a valid type expression.

            stype = string; // type name is type expression

if ( not “Hello”  isa dtype ) std::console().writeline(“Insane”);

 

Multi-type type expression is a collection of different type expressions. The isa operator returns true if the  type of object matches one of the types in a multi-type.

 

            streamtype = < std::file, net::socket >

            s1 = std::file(destfile”,’w’);

            s2 = net::socket(destserver”,  6500);

            if ( s1 isa streamtype and s2 isa streamtype ) std::println(obj = “Good”);

 

Enumerated type is a collection of constants. The isa operator returns true if the value of object equals any one of the collection. Look at the below example:

 

            littleprime = < 1,2,3,5,7 >;

            test = 5 isa littleprime; // test = true

 

The type name for type object is typeinfo. Below shows how it works:

           

            stringtype = string;

            test = stringtype isa typeinfo; // test = true

 

 

6.      Gate Keeper: Typed Property and Variable

There are tools in Luban to enforce strong typing policy if necessary. The type of property can be explicitly declared with type expression in structure interface definition to make sure only the objects of specified type can pass through. And also local variable can be bound to certain type using type expression. The setting of object of wrong type to a typed variable will result in the abortion of structure evaluation or script execution.

 

We can see the use of typed variable from below Luban code:

 

            double dx;

            dx = “Hello”; // evaluation aborts here

            std::console().writeline(“You should never see me, the world should have ended before me”);

 

The above example assigns a string value to a variable “dx” that is a variable with predefined type double. The assignment results in the aborting of the evaluation of the script. So the last line of code should never be executed and the print should never appear on screen.

 

The below example shows the use of typed property in structure interface:

 

            namespace demo;

 

            struct powertoint

            (

                        input:

                                    < double, int > base;

                                    int power;

                        output:

                                    result;

            )

as process

            {

                        result = 1;

                        for( I = 0; I < input.power; I++)

                                    result *= base;

                        output.result = result;

            }

 

We can save the above code to a file “powerint.lbn”. Then we can write another script to call this new component, as below

 

            sqr = demo::powertoint( base = 4, power = 2 ).result; // pwr = 3.1416*3.1416

            std::println(obj=sqr);

            sqrt = demo::powertoint( base = 4, power = 0.5 ).result;

            if ( sqrt isa error ) std::println(obj=”Good, to get square root failed as expected”);

           

            pwrcalc = demo::powertoint();

            pwrcalc.power = 0.5; // evaluation stops here

            std::println(obj=”Surprise! World should have ended before me”);

 

We then save the above script as “testpowerint.lbn” and start the test like the following:

 

Mycomputer > luban powerint.lbn testpowerint.lbn

  16

  Good, to get square root failed as expected

     

We can see the first evaluation of demo::powertoint succeeded and gave result 16 to print. The second structure call having property “power” set to 0.5 that violates the int type of the property results in an error value, causing the printing of “square root failed” message. The line before the last line sets a double value into the property “power” of int type, causing the evaluation to stop. That’s why we don’t see the last line get executed.