Hefeust CORTES
Hefeust CORTES

Reputation: 129

JS : a bit of magic with contextual polymorphism

Should JavaScript have enough expressivity to perform 'contextual' polymorphism?

What I mean is to have an object that exposes different interfaces depending on its internal state, where this mechanism would be implemented using module pattern, method chaining and closures.

For instance: suppose a Finite State Machine:

// definition time
let m = machines_module.factory('machine-01', options);

m.onsuccess(function() { console.log(''done !''); });
m.onfailure(function() { console.log(''error !''); });

m.def('state-1')
  .inital()
  .when('event-a', 'state-2')
  .when('event-b', 'state-3');

m.def('state-2')
  .when('event-a', 'state-3');

m.def('state-3')
  .terminal()
  .when('event-b', 'state-2');
// etc... dozens of defs

// switch to run time
m.go('state-1');

// accept is only visible at run-time
m.accept('event-a');
m.accept('event-b');
// hundreds of transitions

// --> success  or failure

// switch back to def-time
m.halt();

By the way, object signature are context dependent. At definition time you cannot accept events and at runtime you cannot add new states.

Is this possible? I guess it's yes

Since I tested:

 let X = {
   abc : 123,
   del : function() {
     for(let prop in this) {
       delete this[prop];
     }
   },
   xyz : 'will be destroyed by del() method'
 };

which, surprisingly for me, worked... I could now implement the code below...

The idea is to port into ES6 the project:

https://github.com/hefeust/dexm

Which is my first implementation on a finite state machine with time-embedded ability (TEFSM).

I repeat; the basic idea is to offer access to several methods of an object, depending of its internal state (stopped or running), thus to enforce data protection and safety of use.

Here's the code:

'use strict';

// Inside a module pattern
class Utils {
  static eraseProps(obj) {
    console.log('   erase props');
    for(let prop in obj) {
      delete obj[prop];
    }
  };

  static cloneProps(fromObj, toObj) {
    console.log('   clone props');
    for(let prop in fromObj) {
      toObj[prop] = fromObj[prop];
    }
  }
}

class ContextualPolymorphic {
  constructor() {
    this.defs = new Map();
    this.state = false;
  };

  def(name, value) {
    this.defs.set(name, value);
  };

  list() {
    return this.defs.keys();
  };

  go() {
    this.state = true;
  };

  halt() {
    this.state = false;
  };

  defWrapper() {
    let self = this;

    return {
       go() {
         self.go();
         Utils.eraseProps(this);
         Utils.cloneProps(self.wrapper, this);
       },
       def(name) {
         self.def(name);
       }
    };
  };

  runWrapper() {
    let self = this;

    return {
       halt() {
         self.halt();
         Utils.eraseProps(this);
         Utils.cloneProps(self.wrapper, this);
       },
       list() {
         return self.list();
       }
    };
  };

  get wrapper() {
    if(this.state) {
      return this.runWrapper();
    } else {
      return this.defWrapper();
    }
  };
}

// module's factory method
let cp = new ContextualPolymorphic();

// we can solely access to "w",
// it s published outside the module
// w id public, cp is privatized
let w = cp.wrapper;

// tests
console.log('def phase');
console.log(w);
w.def('abc');
w.go();

console.log('run phase');
console.log(w);
w.list();
w.halt();

console.log('ready for new def-phase');
console.log(w);

So, what are your feelings?

Is it possible to write the mechanism of contextual polymorphism simpler? without delete and cloning props into the wrapper getters?

Upvotes: 0

Views: 55

Answers (0)

Related Questions