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.
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:
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.
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();
// 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’);
// 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.
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.
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(“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(“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
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
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:
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:
< double, int > base;
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
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
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.