In the forums there was a discussion about how to wrap existing libraries transparently and with a minimum of overhead. In that discussion an example was given about how to wrap the neural network library brain. To avoid posting the same text here as well, this blog post is about translating another neural network library called ‘Synaptic‘.
Getting Started
When starting, it’s useful to have a look at the JS libraries homepage or GitHub page. In this case both exist and are worth a closer look. The homepage contains 6 demos, which are interesting enough to get us hooked. Beside the demos it contains a link to the documentation, which redirects us to the corresponding GitHub page.
The documentation is divided into 5 different sections, where each section documents a certain JS file. In this context we only have a look at the first section. The other sections/files can be translated in the same manner.
Now let’s start by having a look at the ‘Neurons’ documentation. As you can see there’s not much to read, but at the same time there is no code snippet that looks like a class definition in the first place. Still we can wrap it into a class nicely.
With the sentence
“A Neuron can perform basically 4 operations: project connections, gate connections, activate and propagate”
we can easily imagine a class called “Neuron” with 4 methods. Something like this:
type TNeuron = class procedure project; procedure gate; procedure activate; procedure propagate; end;
In fact – as external code is only interfaced – this is not a real class, but an external class. This means that we only need the definition while the implementation is done by the external JavaScript code.
External Classes
Since external classes are the glue between the Object Pascal and the JavaScript world it is important to mention that these classes are case sensitive. This means that the method ‘project’ will become a ‘project’ in the JavaScript output. Alternatively, you can also use any other favorite naming style, but specify the JavaScript separately. It could look like this then:
TNeuron = class external procedure Project; external 'project'; procedure Gate; external 'gate'; procedure Activate; external 'activate'; procedure Propagate; external 'propagate'; end;
What remains missing are the parameters of the methods. These can be gathered by taking a closer look at the documentation for each method. Let’s start by looking at ‘project’ https://github.com/cazala/synaptic/wiki/Neurons#project
We can find the following code snippet:
var A = new Neuron(); var B = new Neuron(); A.project(B); // A now projects a connection to B
This makes clear that the method ‘project’ has at least one parameter, which is another neuron. The sentence ‘The method project returns a Connection object, that can be gated by another neuron.’ also makes it clear that it outputs a ‘connection’.
Now it’s not really clear what a connection is. So we can only specify this as Variant (can be anything).
Our class looks like this now:
type TConnection = Variant; TNeuron = class function project(value: TNeuron): TConnection; [...] end;
Next, let’s have a look at the gate method. By the code snippet:
var A = new Neuron(); var B = new Neuron(); var connection = A.project(B);
var C = new Neuron(); C.gate(connection); // now C gates the connection between A and B
It got clear that the parameter for the gate method is a connection, so it can be written as:
procedure gate(value: TConnection);
For the method ‘activate’ the following documentation snippet:
var A = new Neuron(); var B = new Neuron(); A.project(B);
A.activate(0.5); // 0.5 B.activate(); // 0.3244554645
hints for a method that can accept either one or no parameter. This can be expressed in Object Pascal with the ‘overload;’ directive.
Thus it becomes to:
procedure activate(value: Float); overload; procedure activate; overload;
Last but not least we have the method ‘propagate’. Its documentation code snippet is a bit bold to print in total, so let’s only focus on the use of the method propagate:
var learningRate = .3; [...] B.propagate(learningRate, 0);
From this you get that it has two parameters (numbers). In the example it remains unclear whether both are of type float or whether the second one is an integer. In case of doubt just always use floats. You can narrow this later at anytime when you have a confirmation that the parameter is really only limited to integer values.
Now we have all methods specified, so the class looks like this now:
type TConnection = Variant; TNeuron = class external function project(value: TNeuron): TConnection; procedure gate(value: TConnection); procedure activate(value: Float); overload; procedure activate; overload; procedure propagate(learningRate: Float; value: Float); end;
This can already be used on existing Neuron objects. Unfortunately, we haven’t specified a constructor so far and thus we can’t easily translate the existing code snippets from the documentation in Object Pascal.
Constructor
A basic constructor is always exposed by DWScript, it’s just not explicitly clear how this looks like. Luckily we have the ‘constructor’ keyword to specify a constructor in Pascal. DWScript typically ignores the specified name (in particular if it is ‘Create’) and uses the class name instead. However, out of convenience we can still use the ‘Create’ name. Thus a constructor can look something like this:
type TNeuron = class external constructor Create; [...] end;
Now if one calls:
TNeuron.Create
it will be translated to
new TNeuron()
which is just slightly different to the
new Neuron();
from the example.
However, this already looks promising, except for the fact that the name is still wrong. It can be fixed by either renaming the class to the JS name or by supplying an external name for the class or for the constructor.
Renaming the class is probably not the best idea as it should fit into the naming scheme. In Smart Mobile Studio a ‘J’ has been picked instead of the ‘T’ for Object Pascal types. This leaves the option to supply a dedicated external name (either for the class or for the constructor)
The example below shows both (only one would be really necessary)
type JNeuron = class external 'Neuron' constructor Create; external 'Neuron'; [...] end;
With this you can already write all the documentation code snippets in Object Pascal.
Final thoughts
If this isn’t enough, you can always go deeper with the header translation by looking at the JavaScript source code. There you can discover whether there are further overloads to the functions or hidden return values. Also you can spot fields, which are mostly used internal, but might become useful if you want to extend the supplied class.
In case of the Synaptic library there are 4 more sections that needed a header translation until it is fully usable. Consider this as homework until the next beta (to be released soon) will contain all translated headers. You can find it in the library path (under ‘Synaptic’).