-
Notifications
You must be signed in to change notification settings - Fork 23
QuantoCore
QuantoCore is the name given to the general purpose ML library for creating and rewriting with string graphs and !-graphs. It also refers to the Controller, which is a command line tool that provides access to all of the core's functionality via a JSON protocol. While the old controller has been retained for backwards compatibility with the old Quantomatic GUI, the new controller and protocol are what we discuss here.
Notable changes from the old controller are that everything is done is JSON (no more escape-sequences), the controller functionality is implemented in modules, and calls to the core are processed and handled asynchronously.
The controller is a thin wrapper for a table of modules. Requests look like this:
{
"request_id": #,
"controller": ...,
"module": ...,
"function": ...,
"input": JSON
}
For every message, the dispatch chain goes: ControllerRegistry
=>
Controller
=> Module
=> function
.
Responses look like this:
{
"request_id": #,
"success": true,
"output": JSON
}
or
{
"request_id": #,
"success": false,
"message": STR,
"code": INT
}
Functions themselves are just maps from json to json. If they return
normally, the first response format is used. If they raise a user_exn,
the second response is used. Modules functorise over GRAPHICAL_THEORY
as well as any modules they depend on.
Modules keep their own state in ref's, expose functions (typically state accessors used by themselves or other modules), and register protocol functions. This method of storing state seems to work fine, provided the functor call for each module is only called once per controller:
(* Foo and Bar share refs, but Baz's refs are distinct from Foo and Bar *)
structure Foo = Fun(X)
structure Bar = Foo
structure Baz = Fun(X)
Function definitions are made such that bindings can be automatically generated by ML code. Central to this is the concept of a protocol type, or ptype. A ptype is one of a short list of type symbols which the client bindings may want to handle in a particular way. Currently, ptypes are defined as:
datatype ptype =
list_t of ptype |
string_t | int_t | json_t |
graphname_t | vertexname_t | edgename_t | bboxname_t | rulename_t |
graphdesc_t | ruledesc_t
For instance, if a function takes a graphname_t as an argument, the java binding will actually take in a graph object, then call getCoreName.
New protocol functions are defined like this:
(* test named args *)
val ftab = ftab |> register
{
name = "concat",
doc = "Concatenates the given arguments",
input = N ["arg1" -: string_t, "arg2" -: string_t],
output = S string_t
} (fn x => (
let
val s1 = arg_str x "arg1"
val s2 = arg_str x "arg2"
in Json.String (s1 ^ s2)
end
))
It's a bit verbose, but if we try to keep modules at < 30 protocol functions a piece it should keep things pretty readable.
Register takes an fdesc followed by a function from json to json. The input and output fields can either be singletons (marked by S) or a collection of named arguments (marked by N). The benefit of this is the core now knows enough to generate documentation or code bindings on the fly. So, the last stage of the build process would be something like:
% quanto-core --java-bindings [dir]
Adding other languages (e.g. scala) would then just be a matter of implementing some ptype handlers.