scriptNOP
A framework for notification-oriented programming paradigm (NOP[1]) implemented in TypeScript.
In the Notification Oriented Paradigm (NOP), there are the "factual and causal smart-entities named as Fact Base Elements (FBEs) and Rules that are related to another collaborative notifier smart-entities. Each FBE is related to Attributes and Methods, whereas each Rule to Premises-Conditions and Actions-Instigations. All these entities collaboratively carry out the inference process using notifications, providing solutions to deficiencies of current paradigms" [1].
This implementation provides state-of-the-art features of NOP, in TypeScript, exploring the current limits of object orientation and imperative programming, parallel programming and concurrent programming. The implementation has no dependencies on other libraries and can be used in any TypeScript/JavaScript runtime or browsers. Also, this implementation is REACTIVE IN DEPTH and optionally accepts FUZZY[2] parameters and CUSTOM FUNCTIONS like sum of a weighted input of a NEURON[3], and you can still combine it all at the same time.
Contents
- Sample application
- Defining Conditions
- Rules
- Instructions to run this project
- Particularities of this implementation
- References
- About
Sample application
This program contains an example of an application called "Target shooting". There is the main thread (state manager), where all the Fact Base Elements are, and there are the secondary threads where the Rules are.
main.ts (main thread):
import {
App,
delay,
FactBaseElement,
} from "https://deno.land/x/script_nop/mod.ts";
App.init({
numThreads: 1,
extensionsURLs: [ //for URL to local file: new URL("./my_file.js", import.meta.url).href
"https://deno.land/x/script_nop/src/extensions/deepEqual.ts",
],
rulesURL: "https://deno.land/x/script_nop/example/rules.ts",
onRuleNotification: (rule: any, msg: any) =>
console.log(
`Received Notification "${JSON.stringify(msg)}" from Rule "${rule}```,
),
});
/*
* Example: Target shooting aplication
*/
class Shooter extends FactBaseElement {
constructor(fbeName: string) {
super(fbeName);
}
shoot() {
super.set(
{
gun: {
bullets: 5,
pull_trigger: false,
},
target: false,
},
);
}
}
const shooter1 = new Shooter("shooter1_name");
await delay(3000); //Wait for all threads to be started
shooter1.shoot();
rules.ts (secondary threads):
import { Rule } from "https://deno.land/x/script_nop/mod.ts";
const rule1 = new Rule(
{
name: "rule1_name",
condition: {
premise: {
fbe: "shooter1_name",
attr: "gun.bullets",
is: ">",
value: 0,
},
},
action: (context: any, notifications: any) => {
console.log("loaded gun!!!");
const bullets = context["shooter1_name"].get("gun.bullets"); // get FBE value.
//const notificationsRule2 = notifications["rule2_name"]); //get notification from "rule2_name" (if "rule2_name" is a dependency).
context["shooter1_name"].set({ // set FBE value, changes will be automatically sent to the state manager thread.
target: true,
}, "IGNORE_MISSING"); //with "IGNORE_MISSING", missing attributes in will not be considered excluded.
//rule to rule notifications are control mechanisms for the NxN relationship between Rules.
return { gun_loaded: true }; //send notification to the state manager thread, if you have Rules that depend on this Rule activated, they will also receive this notification.
},
},
);
To avoid problems with threads, start the "rules.ts" file with the Rules instantiations, putting operations like "await" at the end of the file, after such instantiations.
Defining Conditions
Conditions are implemented in a tree structure, easy for humans to understand. Note that the "." is reserved in this implementation for path notation, and this implementation handles circular references. See examples of Conditions:
//------------------------- TYPES OF CONDITIONS ----------------------
//----------------WITH ONE PREMISE:
const c: Condition = {
premise: {
fbe: "shooter1_name", //Fact Base Element name.
attr: "target.person.age", //path notation
is: ==, //"==", ">", "<", etc. Or: function name (registered extension).
value: true, //non-reactive constant.
},
}
//----------------WITH ONE PREMISE (WITH REACTIVE VALUE):
const c: Condition = {
premise: {
fbe: "shooter1_name", //Fact Base Element name.
attr: "target.person.age", //path notation
is: ==, //"==", ">", "<", etc. Or: function name (registered extension).
value: { //reactive FBEvalue
fbe: "shooter2_name",
attr: "target.person.age",
},
}
}
//----------------WITH ONE SELF-EVALUATED PREMISE:
//The value of the respective attr is already the result of the Premise.
const c: Condition = {
premise: {
fbe: "shooter2_name", //Fact Base Element name.
attr: "target.person.age" //path notation.
}
}
const c: Condition = {
premise: {
fbe: "layer1_name", //Fact Base Element name.
attr: "neurons.0", //path notation.
is: "sumOfWeights", //custom function name, function out is result of the Premise, FBE.attr is input of function
}
}
//----------------WITH OR, AND, XOR
const c: Condition = {
and: [ //keys: "or", "and", "xor"
//ARRAY of sub Conditions.
]
}
//----------------WITH custom function
const c: Condition = {
is: "+", // "function name (registered extension) or operator (+, *, etc)",
sub_conditions: [ //this vector is the input parameter of the function
//ARRAY of sub Conditions.
]
}
//----------------WITH negation
const c: Condition = {
not: c2, //c2 is one object of type Condition.
}
//----------------WITH OPTIONAL parameters for FUZZY logic:
//Fuzzy parameters are optional and combinable with any type of Condition.
const c: Condition = {
// ... (Condition parameters) ...
min_threshold: 0.2, //number (or reactive FBEValue) for fuzzy logic (optional), "if Condition < min_threshold".
max_threshold: 0.8, //number (or reactive FBEValue) for fuzzy logic (optional), "if Condition > max_threshold", you can set a defined range, defining min_threshold and max_threshold at the same time.
}
const c: Condition = {
// ... (Condition parameters) ...
exactly: 0.5, //number (or reactive FBEValue) for fuzzy logic (optional), the result of the expression must be equal to the value.
}
*/
Condition with extensions
There is also an extension interface for named functions, which are used as cuttomized functions in Premises and Conditions. These extensions are defined at the beginning of the main thread by the "extensionsURLs" parameter, but they can also be defined manually in the Rules file:
Rule.registerExtensions([customFunc2]);
How to use extensions:
/*
In Premises:
deepEqual = function with name "deepEqual", ex: export default function deepEqual(a: any, b: any): any { ...
"a" is the result of "FBE.attr" and "b" is the result of "FBE.value"; "b" is optional for self-evaluated Premises.
*/
const c: Condition = {
premise: {
fbe: "shooter1_name",
attr: "character",
is: "deepEqual", //FUNCTION NAME HERE
value: { name: "joe", age: 25 }, //Non-reactive CONSTANT, but it could also be an FBEvalue
},
};
/*
In Conditions:
custonFunc = function with name "custonFunc2", ex: export default function custonFunc2(a: any[]): any { ...
"a" is an array of result of Conditions ("sub_conditions" parameter).
*/
const c: Condition = {
is: "customFunc2", //FUNCTION NAME HERE, the "is" can also be operators like "+", "*", etc.
sub_conditions: [ //"sub_conditions" only exists when the "is" attribute in a Condition is filled
{
premise: {
fbe: "shooter1_name",
attr: "gun.bullets",
is: "==",
value: true, //Non-reactive constant, but it could also be an FBEvalue.
},
},
],
};
Combination of Conditions
A combination of different types of Conditions together is possible. Example with simple logic, fuzzy logic and custom functions:
const c: Condition = {
or: [
{
not: {
is: "ReLU", //custom function name in Condition, input is sub_conditions Array
sub_conditions: [
{
premise: {
fbe: "layer1_name",
attr: "neurons.0", //paths with .N is valid for vectors
is: "sumOfWeights", //custom function name in Premise, input is FBE.attr
},
},
],
},
},
{
premise: {
fbe: "shooter1_name",
attr: "gun.distance",
},
min_threshold: 0.2, //fuzzy parameter, combinable with any type of Condition
},
{
premise: {
fbe: "shooter1_name",
attr: "gun.pull_trigger",
is: "==", //simple logic
value: true,
},
},
],
};
In the library package the extension functions "deepEqual", which checks in depth if two objects are the same, i.e. compares their parameters, subparameters and etc. It is possible for example an extension function that represents a sum of weighted weights of a neuron, it can also be combined with fuzzy logic for the activation threshold of the same.
Rules
See also options for instantiating a Rule:
type RuleOptions = {
name: string;
condition: Condition;
action: (
context: { [key: string]: FactBaseElement },
notifications: Notifications,
) => Promise<any> | any;
delay?: number;
depends?: string[];
};
Instructions to run this project
Basically you just need to clone the project and install the Deno runtime.
# clone project
git clone https://github.com/hviana/scriptNOP.git
# enter the project directory
cd scriptNOP
# install Deno (Mac, Linux)
curl -fsSL https://deno.land/install.sh | sh
# install Deno (Windows/PowerShell)
iwr https://deno.land/install.ps1 -useb | iex
# run project example:
deno run --unstable --allow-read --allow-net --allow-write main.ts
# bundle scriptNOP lib to any runtime or web browsers:
deno bundle mod.ts nop.js
Particularities of this implementation
In this framework, there is a reduction in the casual notifying entities of the NOP core (with the removal of Instigations), with a reduction also of factual entities (with the removal of Methods), promoting expressiveness at the cost of a possible greater coupling. Also, regarding factual entities, there is no explicit entity for Attributes, although their concept still exists. In this way, Actions directly represent a procedure reference that can directly change the values of several FBEs, and allow calls to any other method outside the context and paradigm of NOP. Attributes are not explicit as factual entities are centered on FBEs, which are complex and reactive in depth. It is possible to visualize the behavior of FBEs:
//initial values.
fbe.set(
{
a: {
b: {
c: "foo",
},
d: true,
},
},
);
/*
Rules that use "a", "a.b" or "a.b.c" will be re-evaluated.
Rules that use only "a.d" are not re-evaluated, since
the value of "a.d" has not been modified.
*/
fbe.set(
{
a: {
b: {
c: "bar",
},
d: true,
},
},
);
fbe.get("a.b"); //returns object "a.b".
The framework also has a history feature that saves Rule activations with the respective FBEs values at the time of activation. The changes in the FBEs are also saved in the history, with the information of the Rule name of the Action that made such changes.
It is also considered the dependency of Rules: A Rule "B" can depend on a Rule "A", and the Rule "B" is only evaluated automatically if the Rule "A" is satisfied, and this dependency is also implemented in the notification style (Rules to Rules notification). A Rule can have several dependencies and be dependencies on several others, in an NxN relation. When a Rule is executed, the return of its Action is passed as a notification to the other Rules that depend on it. A Rule knows that it has satisfied its dependencies when it has received notifications from all dependencies. It is always considered the last notification sent, and when the Rule is activated, your received notifications are cleared.
An application with this framework can result in a "freeze" of the program if infinite changes of Fact Base Elements states start, given the respective Actions. To minimize this problem and at the same time implement the priority idea of Actions and Rules, when creating a Rule it is possible to insert an optional delay for its Action. Note that there is no need for a "Dispatcher" to queue notifications, as such notifications are implemented using async functions with delay.
The code is very dense, although every detail has been thought of in order to favor readability and avoid replication. With TypeScript, we have a new way of defining types and programming in an object-oriented style compared to classic object-oriented languages such as Java and C++, which drastically reduces the amount of code. See the following code snippet:
export interface FBEvalue {
fbe: string;
attr: string;
}
export interface Premise extends FBEvalue {
is: string;
value: any | FBEvalue;
}
// ...
interface ConditionWithXor extends FuzzyCondition {
xor: [Condition, Condition, ...Condition[]]; //min 2 Conditions
}
export type Condition =
| ConditionWithNot
| ConditionWithPremise
| ConditionWithAnd
| ConditionWithOr
| ConditionWithXor
| ConditionWithFunc;
//...
export class Rule {
static #extensions: {
[key: string]: Function;
} = {};
static initialized: boolean = false;
#transpiledCondition: (
context: { [key: string]: FactBaseElement },
) => Promise<boolean>;
References
[1] J. M. Simão, C. A. Tacla, P. C. Stadzisz and R. F. Banaszewski, "Notification Oriented Paradigm (NOP) and Imperative Paradigm: A Comparative Study," Journal of Software Engineering and Applications, Vol. 5 No. 6, 2012, pp. 402-416. doi: https://www.doi.org/10.4236/jsea.2012.56047
[2] Melo, Luiz Carlos & Fabro, João & Simão, Jean. (2015). Adaptation of the Notification Oriented Paradigm (NOP) for the Development of Fuzzy Systems. Mathware& Soft Computing. 22. 1134-5632. url: https://www.researchgate.net/publication/279178301_Adaptation_of_the_Notification_Oriented_Paradigm_NOP_for_the_Development_of_Fuzzy_Systems
[3] F. Schütz, J. A. Fabro, C. R. E. Lima, A. F. Ronszcka, P. C. Stadzisz and J. M. Simão, "Training of an Artificial Neural Network with Backpropagation algorithm using notification oriented paradigm," 2015 Latin America Congress on Computational Intelligence (LA-CCI), 2015, pp. 1-6, doi: https://doi.org/10.1109/LA-CCI.2015.7435978
About
Author: Henrique Emanoel Viana, a Brazilian computer scientist, enthusiast of web technologies, cel: +55 (41) 99999-4664. URL: https://sites.google.com/site/henriqueemanoelviana
Improvements and suggestions are welcome!