domenic / proposal-blocks

Former home of a proposal for a new syntactic construct for serializable blocks of JavaScript code
215 stars 5 forks source link

Functions serializable by default #10

Closed freddi301 closed 6 years ago

freddi301 commented 6 years ago

I think this proposal could be less intrusive. Just let's suppose every object has a function to serialize itself into a JSON like

Object.prototype[Symbol.reify] = function(){
  return { src: "{}" } // where src is a string of code that generates the object minus equality
}

Calling this method on a function

function myFunction() { return 4; }
myFunction[Symbol.reify]() // returns
{
  "src": "function myFunction() { return 4; }",
  "srcMap": "eventual source map"
}

We can then transfer the JSON over the wire or between workers.

Then on the other end

const myFunction = Object.fromReified({ "src": "function myFunction() { return 4; }" });
myFunction() // 4

If there are some closures, they get resolved when we call [Symbol.reify]()

Date.prototype[Symbol.reify] = function(){
  return { "src": `new Date(${this.toJSON()}` }
}
const today = new Date();
const clojureDay = () => today;
clojureDay[Symbol.reify]() // returns
{
  "src": "() => today",
  "scope": {
    "today": { "src": "new Date(\"2018-05-16T10:06:14.853Z\")" }
  }
}

Basically implementing reify method everywhere, we could have javascript fully serializable.

const four = 4;
const add = a => b => a + b;
const addFive = add(5)
const simpleMath = () => addFive(four)
simpleMath.[Symbol.reify]() // returns
{
  "src":  "() => addFive(four)",
  "scope": {
     "four": { "src": "4" },
     "addFive": {
        "src": "add(5)",
        "scope": {
          "add": { "src":  "a => b => a + b" }
        }
     }
   }
}
domenic commented 6 years ago

Dupe of #5.

freddi301 commented 6 years ago

I modified the issue. I think this proposal could have a broader scope. Please consider re-reading @domenic

domenic commented 6 years ago

Thanks. I'm not interested in working on the problem of user-accessible string or AST serializations, partially because it defeats the performance benefits making it unusable for the use cases I'm hoping to solve. Sounds like a separate proposal to me. (Or you can just use .name and .toString() instead of .reify().)

freddi301 commented 6 years ago

It's more about making the whole language serializable (clojures, builtin instances like Date...).

The engine has anyway access to all the needed information (it's enough to see a js debugger in action) at runtime to serialize let's say a clojure, so the cost will be paid only at serialization time.

So the thing you want is the guarantee is that block of code doesn't contain free variables.

Here a jslint/eslint rule can do the job:

function add(a){ noclojure: { return b => a + b } }
// ---------------------------------------^--------
// non local variable "a" used in a serializable code block

Then it could be serialized as

const task = async () => {
  noclojure: {
    const res = await fetch("people.json");
    const json = await res.json();
    return json[2].firstName;
  }
};
const stringSerializedTask = task.toString()
// or
task |> serialize |> sendOverWireOrToWorker

where serialize can return something like this for performance reasons.

Instead of introducing new syntax for variable capture a technique can be

const task = () => {
  noclojure: {
    return async file => {
      const res = await fetch("people.json");
      const json = await res.json();
      return json[2].firstName;
    };
  }
};
worker.postMessage({ task: task.toSource(), parameters: ["people.json"] })

worker file

onmessage = ({ data: { task, parameters } }) => eval(task)()(...parameters)

where eval could be a more performant version of eval which doesn't need access to outer scope (JS engines parses functions lazily anyway).

domenic commented 6 years ago

Yep, I understand. You're proposing a different proposal, and I encourage you to do that in your own repository, not this one.