xp: Semantic JSON (playing with JS proxies)

  • 7 Replies
  • 1395 Views
*

Zero

  • Trusty Member
  • ********
  • Replicant
  • *
  • 732
    • Thinkbots are free
xp: Semantic JSON (playing with JS proxies)
« on: January 08, 2018, 04:05:32 pm »
Watch closely!

Code: [Select]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Semantic JSON</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="here">
</div>
<script>

// 3 objects.
// We tag keys with types. Types come first between angle brackets.

x= {
"<relation>ownership": {
"<slot>owner": "<person>Janis",
"<slot>owned": "<vehicle>Mercedes"
}
};

y= [
"<behavior>select",
"<behavior>open the door",
[
"<behavior>sequence",
"<behavior>unlock the door",
"<behavior>open the door"
]
];

z= {
"<name>name": "John Doe",
"<years>age": 32,
"<job>occupation": "developer",
"<people>siblings": [
"Arthur Doe",
"Sarah Doe"
],
"<lifeFormParents>parents": {
"<person>father": "Homer Doe",
"<person>mother": "Ema Doe"
}
};

// That's our proxy handler.

handler = {

get (target, key) {

var keys = Object.keys(target);

for (var k in keys) {

if (keys[k][0] === "<") {

var tagsend = keys[k].search(">");

if (key === keys[k].substring(tagsend+1)) {

if (reactOnGet[keys[k].substring(1,tagsend)])

reactOnGet[keys[k].substring(1,tagsend)](key,target[keys[k]]);

if (typeof target[keys[k]] === 'object')

return new Proxy(target[keys[k]],handler);

else

return target[keys[k]];
}
}
}
}
}

// This is our "get" reactor. We could do the same on "set" or any other proxy trap.

reactOnGet = {

relation: function(key,val) {
console.log(`read a relation, key:${key}`);
},

slot: function(key,val) {
console.log(`read a slot, val:${val}`);
}
}

// We set a proxy on x.

var px = new Proxy (x, handler);

// We're ready to access its members.

document.getElementById('here').innerHTML = px.ownership.owned;

</script>
</body>
</html>
Thinkbots are free, as in 'free will'.

*

8pla.net

  • Trusty Member
  • **********
  • Millennium Man
  • *
  • 1035
    • 8pla.net
Re: xp: Semantic JSON (playing with JS proxies)
« Reply #1 on: January 09, 2018, 01:36:29 am »
A few thoughts...

Code: [Select]
console.log("read a relation, key: "+key);

console.log("read a slot, val: "+val);


read a relation, key: ownership
read a slot, val:<vehicle> Mercedes


Browser output: Mercedes

Good sample code you posted there.

My Very Enormous Monster Just Stopped Using Nine

*

ranch vermin

  • Not much time left.
  • Terminator
  • *********
  • 958
  • Its nearly time!
Re: xp: Semantic JSON (playing with JS proxies)
« Reply #2 on: January 09, 2018, 06:54:22 am »
it needs a motivation yet?

*

Zero

  • Trusty Member
  • ********
  • Replicant
  • *
  • 732
    • Thinkbots are free
Re: xp: Semantic JSON (playing with JS proxies)
« Reply #3 on: January 10, 2018, 10:11:31 pm »
The first version was a 2 hours hack, my first contact with proxies.
Here is a version that's faster and cleaner.

Code: [Select]
<script>

// the global proxy manager
globalProxy = {

    handler: {

        get (target, key, receiver) {

            // if we have a reaction, react
            if ((typeof target !== 'function') && (target['<tag>'+key]))
                if (globalProxy.react.onGet[target['<tag>'+key]])
                    globalProxy.react.onGet[target['<tag>'+key]](key, target[key]);

            return target[key];
        },

        set (target, key, value, receiver) {

            target[key.strPart] = globalProxy.prepare(value, true);

            // keep tag for later use
            Object.defineProperty(target, '<tag>'+key.strPart, {
                enumerable: false,
                value: key.tagPart
            });

            // if we have a reaction, react
            if (globalProxy.react.onSet[key.tagPart])
                globalProxy.react.onSet[key.tagPart](key, target[key.strPart]);
        },

        apply (target, thisArg, argList) {

            // an id to match before-call and after-call
            var callId = Symbol();

            var haveReaction = (
                thisArg['<tag>'+Object.keys(thisArg)[0]]
                in
                globalProxy.react.onApply);

            if (haveReaction)
                globalProxy.react.onApply[thisArg['<tag>'+Object.keys(thisArg)[0]]](
                    Object.keys(thisArg)[0],
                    thisArg,
                    argList,
                    false,  // is call done yet?
                    callId);

            var result = Reflect.apply(...arguments);

            if (haveReaction)
                globalProxy.react.onApply[thisArg['<tag>'+Object.keys(thisArg)[0]]](
                    Object.keys(thisArg)[0],
                    thisArg,
                    argList,
                    true,   // is call done yet?
                    callId);

            return result;
        }
    },

    // recursively prepare an object, making a proxy out of it
    prepare: function (obj) {

        if (typeof obj === 'function') return new Proxy(obj, globalProxy.handler);

        if (typeof obj !== 'object') return obj;

        var result = {};

        var keys = Object.keys(obj);

        for (var k=0; k<keys.length; k++) {

            result[keys[k].strPart] = globalProxy.prepare(obj[keys[k]]);

            // keep tag for later use
            Object.defineProperty(result, '<tag>'+keys[k].strPart, {
                enumerable: false,
                value: keys[k].tagPart
            });
        }

        return new Proxy(result, globalProxy.handler);
    },

    // proxy traps reactor
    react: {

        onGet: {

            // for every <relation> tag
            relation: function(key, val) {

                console.log(`reading a relation, key:${key}`);
            },

            // for every <slot> tag
            slot: function(key, val) {

                console.log(`reading a slot, val:${val}`);
            }
        },

        onSet: {
        },

        onApply: {

            // for every <math> tag
            math: function(key, thisArg, argList, done, callId) {

                var al = JSON.stringify(argList);

                var when = done ? 'after' : 'before';

                console.log(`${when} calling a math function, arguments:${al}`);
            }
        }
    }
};



// a global getter to create objects
Object.defineProperty((window || global), 'object', { get : function() {

    return globalProxy.prepare({});
}});



// get the "tag" part of a string
Object.defineProperty(String.prototype, 'tagPart', { get : function() {

    if (this[0] !== '<')

        return '';

    else {

        var tagsEnd = this.search('>');

        if (tagsEnd < 0)

            return '';

        else

            return this.substring(1, tagsEnd);
    }
}});



// get the "str" part of a string
Object.defineProperty(String.prototype, 'strPart', { get : function() {

    if (this[0] !== '<')

        return this;

    else {

        var tagsEnd = this.search('>');

        if (tagsEnd < 0)

            return this;

        else

            return this.substring(tagsEnd+1);
    }
}});



// check if string has tag
Object.defineProperty(String.prototype, 'hasTag', { get : function() {

    return ((this[0] === '<') && (this.search('>') > -1));
}});



// register a hook
function sense(event, tag, reaction) {

    globalProxy.react[event][tag] = reaction;
}



// playground to test things


sense('onSet', 'slot', function(key, val) {

    console.log(`setting a slot, val:${val}`);
});


workspace = object;

workspace.foo = {
    "<relation>ownership": {
        "<slot>owner": "<person>Janis",
        "<slot>owned": "<vehicle>Porsche"
    }
};

workspace.foo.ownership["<slot>owned"] = "<vehicle>Mercedes";

document.getElementById('here').innerHTML = JSON.stringify(workspace.foo.ownership);

workspace.fun = {

    "<math>add": function(a, b) { return a+b; },

    "<math>mul": function(a, b) {

        workspace.foo["<slot>result"] = 0;

        for (var i=0; i<b; i++)
            workspace.foo["<slot>result"] =
                workspace.fun.add(a,workspace.foo.result);

        return workspace.foo.result;
    }
};

document.getElementById('here').innerHTML += '<br>'+workspace.fun.mul(3,4);

document.getElementById('here').innerHTML += '<br>'+workspace.foo["<tag>ownership"];

</script>

ED: upgrade - function calls are supported now
ED: bug fix
« Last Edit: January 12, 2018, 01:30:23 pm by Zero »
Thinkbots are free, as in 'free will'.

*

Zero

  • Trusty Member
  • ********
  • Replicant
  • *
  • 732
    • Thinkbots are free
Re: xp: Semantic JSON (playing with JS proxies)
« Reply #4 on: January 11, 2018, 03:49:03 pm »
Thank you 8pla.net  :)

Quote
it needs a motivation yet?
Sorry, I didn't understand.
Thinkbots are free, as in 'free will'.

*

Zero

  • Trusty Member
  • ********
  • Replicant
  • *
  • 732
    • Thinkbots are free
Re: xp: Semantic JSON (playing with JS proxies)
« Reply #5 on: January 12, 2018, 08:53:40 am »
A little "what's that and why should I care".

Semantic JSON is about tagging JSON keys and strings with a semantic tag. It can be seen as a typed version of JSON, that still respects the original syntax of JSON.

For example, in typical JSON, you could have:

Code: [Select]
workspace.foo = {
    "ownership": {
        "owner": "Janis",
        "owned": "Mercedes"
    }
}

Sorry Janis, I love you ;) But after all, strictly speaking, "ownership" is just a Javascript object, and "Janis" and "Mercedes" are just Javascript strings.

The developer of a library, or the community of users of a library, can choose to follow a convention like: in an "ownership", "owner" is always a human and "owned" is always an object. But the truth is that "Mercedes" is a string that could represent a brand as well as the name of a tap dancer.

Now if we use Semantic JSON:

Code: [Select]
workspace.foo = {
    "<relation>ownership": {
        "<slot>owner": "<person>Janis",
        "<slot>owned": "<vehicle>Mercedes"
    }
};

We make it clear that "ownership" is a relation, that "owner" and "owned" are relation slots, that "Janis" is a person and that "Mercedes" is a vehicle. I say it again: "Mercedes" is a vehicle, not a brand. We could have been talking about the "Mercedes" brand, but we're not. Here we're talking about a vehicle.

What that means is that Semantic JSON can directly represent real life objects, events, concepts, ...etc. Which is a huge step forward, IMO.

Ok, nothing spectacular. We have RDF and OWL for instance, so Semantic JSON doesn't bring any magical trick on the table. But it is convenient, lightweight, easy to use. It will integrate nicely with JSON Schema (that's the next step), and there should be a bridge to and from RDF and OWL later.



The idea came from another thread, where I set up a syntax for relations, semi-structured data, and behaviors. You remember this:

Code: [Select]
namespace prefix : "filename"

item name -> [ meta | data ]

item name -> relation type { slot1: item1, item2, ... ; slot2: item3; }

item name -> ( behavior node, ... , ... )

It's all good, the syntax is clean, and it's easy to parse with PEGjs for instance. But I wanted to be able to express these directly in JSON, because I wanted it to be well integrated in Javascript.

Ultimately, the aim is to implement the huge and ever growing list started in the "States / Processes / EventActs" thread.



The "why" of the proxy thing came from the same other thread, when I tried to make a tiny conscious program. You remember this:

Code: [Select]
function log(x) { console.log(JSON.stringify(x)); }


var entity = {};


entity.externalSensors = [0, 0, 0, 0, 0, 0, 0, 0];


entity.externalActuators = [0, 0, 0, 0];


entity.internalSensors = [0, 0, 0, 0];


entity.instantMemory = [
    [0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];


entity.target = [18, 1, 2, 3];


entity.behavior = {

    3: [0, 2, 3, 0],
    5: [0, 1, 0, 0],
    8: [1, 0, 2, 0]
}


entity.setActuator = function(actuatorId, newValue) {

    entity.externalActuators[actuatorId] = newValue;

    entity.internalSensors = [0, actuatorId, newValue, 0];
}


entity.refreshInstantMemory = function() {

    entity.instantMemory.push(
        entity.externalSensors
            .concat(entity.internalSensors)
            .concat(entity.externalActuators)
    );

    entity.instantMemory.shift();
}


entity.logInstantMemory = function() {

    for (var i=0; i<entity.instantMemory.length; i++)
        console.log(JSON.stringify(entity.instantMemory[i]));
    console.log();
}


entity.setTarget = function(targetSlot, newAddress) {

    entity.target[targetSlot] = newAddress;

    entity.internalSensors = [1, targetSlot, newAddress, 0];
}


entity.xy = function(n) { return { x: n%16, y: Math.floor(n/16) }; }


entity.fetch = function() {

    var values = [];

    for (var t=0; t<4; t++) {

        var addr = entity.xy(entity.target[t]);
        values.push(entity.instantMemory[addr.y][addr.x]);
    }

    return values
}


entity.interpret = function(values) {

    var candidate = 0;

    for (var i1=0; i1<3; i1++) {

        for (var i2=i1+1; i2<4; i2++) {

            if ((values[i1]==values[i2]) && (values[i1]>candidate))

                candidate = values[i1];
        }
    }

    return candidate*2;
}


entity.do = function(action) {

    if (action[0]==0) setActuator(action[1], action[2]);
    if (action[0]==1) setTarget(action[1], action[2]);
}



entity.run = function() {

    while (1) {

        entity.refreshInstantMemory();

        entity.do(
            entity.behavior[
                entity.interpret(
                    entity.fetch())]);
    }
}


entity.run();

This brings the idea of "internal sensors" by which a program can inspect its behavior as if it were observing something external (like another program). I think this capability (self observing) is essential if we're gonna make a conscious program.

Semantic JSON as it is today allows setting hooks on the usage of semantic tags. Once the hook is set, we use the "observable" objects as usual, no special syntax or weird thing: everything is automatic.

There's only one thing. The tag tag is forbidden, because it is used internally to store the semantic tag associated with a key.



If you look closely, you'll notice that a semantic tag is not associated with the described object. Instead, it is associated with the key which holds the reference to that object. This is by design.

An object X can be referenced several times by different keys, in different objects. The semantic tags represent views of the object X, not the object itself. Think about it: Janis can be the mother of someone, and the sister of someone else. So, "mother" and "sister" are not associated with Janis, they are views which are associated with the references to Janis in her child and her brother.



Function calls are observable too. The "sense" hook is called twice: once before the call, and once after the call. The hook handler is given a CallId which uniquely identifies the function call, so we can know which "before call" corresponds to an "after call".

When using observed functions, a dev should always use atomic parameters as arguments. In other words, use only variables, or constants, or function calls.

Code: [Select]
// good call, we have 1 variable and 1 constant:
workspace.fun.mul(workspace.foo.x, 3);

// bad call, we have a sum and a constant:
workspace.fun.mul(workspace.foo.x + workspace.foo.y, 3);

If we use a sum as argument, the observer won't be able to understand where the argument came from.

Also, make observable functions for everything, including the simplest things like sums. If you don't, things won't be observed. Soon I'll try to make clean loop observable functions, and also conditional observable function. It will probably end up looking like RoyalScript.

The point of observing functions would be, ultimately, to make the program understand how it works internally, and modifying itself on purpose.



One thing I'll probably do soon is to log events in a memory structure. The interface should allow choosing whether or not we want to memorize events associated with tag X, how many events we want to memorize, for how much time we want to memorize, ...etc.

Next, the roadmap is like: link it to Javascript Events API, then integrate with JSON Schema, then bridge it to/from RDF and OWL with translation functions.

:)
« Last Edit: January 12, 2018, 11:23:04 am by Zero »
Thinkbots are free, as in 'free will'.

*

ivan.moony

  • Trusty Member
  • **********
  • Millennium Man
  • *
  • 1108
    • Some of my projects
Re: xp: Semantic JSON (playing with JS proxies)
« Reply #6 on: January 12, 2018, 09:12:02 am »
Very clever, to use string as a type + variable. Do you mind if I steal the idea? I mean something like:
Code: [Select]
{"int a" : "4"}
which could be type checked with proxies.
Dream big. The bigger the dream is, the more beautiful place the world becomes.

*

Zero

  • Trusty Member
  • ********
  • Replicant
  • *
  • 732
    • Thinkbots are free
Re: xp: Semantic JSON (playing with JS proxies)
« Reply #7 on: January 12, 2018, 09:43:35 am »
Thank you ivan.moony. Sure go ahead, that's why I'm sharing!
I'm still augmenting my previous post, please check in a few hours.

Ed: done
« Last Edit: January 12, 2018, 11:26:15 am by Zero »
Thinkbots are free, as in 'free will'.

 


MIT reshapes itself to shape the future
by Tyler (Robotics News)
Today at 12:01:15 pm
Tag... you're it...
by Art (AI News )
Today at 02:55:01 am
In need of a psychological coach?
by ruebot (AI News )
October 13, 2018, 01:33:37 pm
Leela Chess Zero
by Art (AI News )
October 08, 2018, 03:30:42 pm
Alpha GO Zero
by Art (AI News )
October 08, 2018, 02:48:32 pm
Detecting fake news at its source
by Tyler (Robotics News)
October 06, 2018, 12:00:55 pm
Phase Change Memory
by Art (AI News )
October 05, 2018, 01:21:59 pm
Model helps robots navigate more like humans do
by Tyler (Robotics News)
October 05, 2018, 12:01:40 pm

Users Online

55 Guests, 1 User
Users active in past 15 minutes:
ivan.moony
[Trusty Member]

Most Online Today: 99. Most Online Ever: 208 (August 27, 2008, 09:36:30 am)

Articles