Reflection Tutorial
A complete dynamic reflection mechanism has been built into Luban language. All objects and components can be dynamically constructed by string name that is only determined at run time. All structure properties can be dynamically set and get with string property name. And all object member functions can be called via string name too. The below sections describe more details.
You can construct any data objects and components by their full names. The proxy for reflective object and component construction is a data type named “luban::ns” that represents the namespace of Luban. By calling the create() member function of luban::ns object with the name string of object and component type, you can construct objects and components dynamically, as the below sample Luban script shows.
// make a namespace
object
ns = luban::ns();
//Basic type
construction
pi = ns.create("double", 3.1415);
i
= ns.create("int", 3);
b = ns.create("bool");
c = ns.create("char", 'a');
s = ns.create("string", "Hello,
Reflection")
v = ns.create("vector", 10, 0);
m = ns.create("map");
s = ns.create("set");
socket = ns.create(“net::socket”, “localhost”, 8500);
//component
construction
adder = ns.create("luban::demo::simpleadder");
adder.(op1=100,
op2=200);
std::println(obj="component output: "+string(adder.result));
From above code you can see you can call the object construction by its type name and pass in arbitrary number of arguments following the string type name. The whole mechanism is dynamic. The below code constructs a default instance of the same type as variable x whatever the type is.
xdefault
= luban::ns().create(string(typeof(x));
Not only you can create any object instance using reflection, you can also get the type information object using reflection and use that object for type checking purpose. Take a look below code.
ns = luban::ns();
filetype
= ns.gettype(“std::file”);
fileobj
= std::file(“xfile”, ‘r’);
truth = fileobj isa filetype;
// truth = true
In above code, a “filetype” object is created by calling “gettype” member function of luban::ns. The created object is then used to check the type of a variable named “fileobj”.
The component model of Luban is based on its struct type that is property value flow based. The Luban’s reflection mechanism covered set and get of struct properties by reflection, as the below code shows.
adder =luban::demo::simpleadder();
adder.pset("op1",
1.0);
result = adder.pget("result");
std::println(obj="component output: "+string(result));
adder.pset({"op1":1.0,
"op2":2.0});
result = adder.pget("result");
std::println(obj="component output: "+string(result));
adder.pset(["op1",”op2],
[1,2]);
result = adder.pget("result");
std::println(obj="component output: "+string(result));
inputs = adder.pget(["op1","op2"]);
std::println(obj="component inputs: "+string(inputs));
Above code shows that you can set and get struct properties by using its built-in member function pset and pget, with property name string passed in as argument. You can set multiple properties at once by passing a map with property names as keys or a vector of keys plus a vector of values. To get property values pget function can be used and if multiple values need to be retrieved at once a vector of property names can be used as arguments. And a vector of values is returned as result.
The member function of any Luban data type can be called via reflection too. As below code shows.
mp = {"John":1,
"Paul":2, "George":3};
mp.invoke("insert",
"Yoko", 4);
std::println(obj=mp);
The special member function invoke is the entrance for relective member function call, and invoke is available for all Luban types. In the above example, the insert member function of map type is called through invoke and a pair of key and value is inserted as result.
There is one related member function allfuncs() that is available for all types in Luban, which is to give out a complete map of all available member functions and their description in a map. Below code shows how to use the special allfuncs().
funcmap = anyobj.allfuncs();
Using Luban’s reflection mechanism and network streaming facilities, it is almost trivial to construct a client/server tool to enable remote calling of Luban components. On the client side, all need to be done is to send the component name, input property values plus the desired output property names. For the server, it needs to listen to the incoming connection get the component name, inputs and desired output names. Server then builds the component by name, set its inputs, get its output as required and send results back through the same socket. The whole system can be built with very little Luban code. That’s why we can list the whole system code as in less than two pages below.
//
a multi-threaded RCC server
namespace
luban::demo;
struct RCCServer( store readwrite
int port;)
as
process
{
rootlistener
= net::listener(store.port);
while ( true )
{
onesock
= rootlistener.accept(); // receive incoming call
::HandleRCC(socket=onesock) & //
dispatch to a different thread
}
}
//
HandleRCC actually handle one call and return results
struct HandleRCC
(
input:
socket;
)
as
process
{
socket = input.socket;
package = socket.readobj();
// get the whole package
compname =
package[0]; // first one is component name
args =
package[1]; // second one input properties
resultprps
= package[2] //third one for desired
outputs;
comp = luban::ns().create(compname); // construct by reflection
comp.pset(args); // property
setting by reflection
result = comp.pget(resultprps); // property get by reflection
socket.writeobj(result);
}
There are two struct are defined in above code. One is the main loop of the RCC server, all it does is to listen to the incoming connection and dispatch to the struct HandleRCC in a different thread then back to listening again. Thread dispatching is a common technique used in server coding to accommodate more than one request simultaneously.
In the struct HandleRCC, a package is retrieved from socket. And component name, inputs and desired outputs are unpacked. Then using reflection mechanism, component is built, its inputs are set and outputs are gotten. Then the results are written back to the same socket.
//
Remote Component Call client code
namespace
luban::demo;
struct RCCClient
(
store readwrite
int port; store readwrite string host;
input:
string compname;
map ins;
outs;
output results;
)
as
process
{
onesock
= net::socket(store.host, store.port);
onesock.writeobj( [input.compname, input.ins, input.outs ] );
output.results
= onesock.readobj();
}
The RCC client code is even simpler. All it does it to make a connection to the specified server host and port. Then client put user specified component name, inputs and desired outputs into a package of vector format, write to the socket. The last step is simply waiting for the results to come back from server.
The below line of Luban script will start the RCC server at port 9600.
luban::demo::RCCServer(port=9600);
The below several lines of code dispatch the work separated to multiple parts and send them simultaneously to the server and wait for them to finish.
client = luban::demo::RCCClient();
client.host
= “superserver.mycompany.com”;
client.port
= 9600;
P1=client(compname=”myns::supercalc” , inputs={“vectosum”:[1,2,3,4]}, outs=”sumofvec”).results &
P2= client(compname=”myns::supercalc”, inputs={“vectosum”:[5,6,7,8]}, outs=”sumofvec”).results &
P3 = client(compname=”myns::supercalc”, inputs={“vectosum”:[9,10,11,12]},
outs=”sumofvec”).results &
waitfor
P1,P2,P3;
total = P1+P2+P3;
Above code construct a RCC client to talk the server running on “superserver.mycompany.com” at port 9600. Through the connection, client calls the component named “myns::supercalc” to summarize numbers in a vector. It dispatches three calls in different thread to summarize three vectors simultaneously, wait for them to come back. Finally it then adds up the three results to get the grand total.
Using reflection and networking, we build a very practical and useful RCC client/server system. There many ways to improve it in practice. One straight forward way is to run the RCC server on a virtual server that may dispatch jobs to a server cluster. Since cluster can probably handle the load balancing by itself, it is transparent to Luban user. For Luban user the unlimited power of parallel computer is now available at their finger tips.
From above example RCC system, we can see that it is powerful to combine reflection and parallel network computing together in Luban. Luban makes a perfect environment to dispatch parallel jobs and synchronize them. With Luban, you have the infinite capacity of cheap computing power from server farm. You can attach problem of any complexity as far as the problem itself can be divided and conquered as smaller problems.