We are using gremlin-javascript and have recently started to define a DSL to simplify our queries.
I am not sure if I've overlooked some caveat, but when attempting to use DSL methods within a repeat step, I consistently receive (...).someDslFunction is not a function errors, but using the same DSL function outside of repeat works without issue.
Here is a short (contrived) DSL definition that produces this issue:
class CustomDSLTraversal extends GraphTraversal {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode);
}
hasNotLabel(...args) {
return this.not(__.hasLabel(...args));
}
filterNotLabel(...args) {
return this.filter(__.hasNotLabel(...args));
}
}
class CustomDSLTraversalSource extends GraphTraversalSource {
constructor(graph, traversalStrategies, bytecode) {
super(graph, traversalStrategies, bytecode, CustomDSLTraversalSource, CustomDSLTraversal);
}
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
...gremlin.process.statics
};
const __ = statics;
const g = traversal(CustomDSLTraversalSource).withRemote(connection);
And here are two uses of it, the first works without issue, the second causes the __.outE().(...).filterNotLabel is not a function error.
g.V('foo').outE().filterNotLabel('x', 'y').otherV(); // No errors
g.V('foo').repeat(__.outE().filterNotLabel('x', 'y').otherV()).times(1); // Error
// __.outE(...).filterNotLabel is not a function
EDIT: Thanks #stephen for pointing out the now so obvious issue:
I had redefined callOnEmptyTraversal for use with our DSL, and foolishly destructured the standard TinkerPop anonymous traversals into our custom ones. These obviously are calling the original callOnEmptyTraversal which does indeed use an instance of the base GraphTraversal.
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
const statics = {
hasNotLabel: (...args) => callOnEmptyTraversal('hasNotLabel', args),
mapToObject: (...args) => callOnEmptyTraversal('mapToObject', args),
...gremlin.process.statics // Whoops
};
const __ = statics;
SOLUTION: Just in case anyone else runs into this scenario. This is how I solved the issue of merging our DSL anonymous traversal spawns with the standard TinkerPop ones:
function callOnEmptyTraversal(fn, args) {
const g = new CustomDSLTraversal(null, null, new Bytecode());
return g[fn].apply(g, args);
}
function mapToCallOnEmptyTraversal(s, fn) {
s[fn] = (...args) => callOnEmptyTraversal(fn, args);
return s;
}
const statics = ['hasNotLabel', 'mapToObject']
.concat(Object.keys(gremlin.process.statics))
.reduce(mapToCallOnEmptyTraversal, {});
const __ = statics;
I assume that the problem is that it's because you start your traversal with __ which is the standard TinkerPop spawn for anonymous traversals. As a result you get a GraphTraversal created rather than your CustomDSLTraversalSource. The TinkerPop gremlin-javascript documentation states that:
steps that are made available on a GraphTraversal should also be made available as spawns for anonymous traversals
So you probably should have your own version of __ that returns the CustomDSLTraversalSource. If you want to see more explicitly where things are going wrong, see in the code that callOnEmptyTraversal() returns GraphTraversal and obviously your DSL methods won't be available on that class.
Related
I am writing a library which is responsible to make some abstracted network calls and transform responses. The way it is going to be consumed is
clients will call init function to create a common/singleton object
as per business needs, clients will call other functions. These functions will use the object created in step 1.
const myLibrary = (() => {
let myLibraryClient;
const init = (clientSpecificInformation) => {
myLibraryClient = anotherExternalLibrary.create(clientSpecificInformation)
}
const runUpwards = () => {
return transform(myLibraryClient.run('5m', 'up'));
}
const runDownwards = () => {
return transform(myLibraryClient.run('5m', 'down'));
}
return {
init,
runUpwards,
runDownwards
}
}
This is just an example. In reality, there will be n functions which need the common (singleton mostly) object. But this pattern will disallow tree-shaking. In such a setup, how can I change the design so that each function is distinctly exported from this library, only when it will be tree-shaken by bundler.
The problem is rather simple. We need to imbue a function with a parameter, and then simply extract that parameter from the body of the function. I'll present the outline in typescript...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Let's set the scene:
Assume strict mode, no arguments or callee. Should work reasonably well on the recent-ish version of v8.
The function passed to assign() must be an anonymous arrow function that doesn't take any parameters.
... and it's alsoasync. The assigned value could just be stored somewhere for the duration of the invocation, but because the function is async and can have awaits, you can't rely on the value keeping through multiple interleaved invocations.
this.getAssignedValue() takes no parameters, returning whatever we assigned with the assign() method.
Would be great to find a more elegant solution that those I've presented below.
Edit
Okay, we seem to have found a good solid solution inspired by zone.js. The same type of problem is solved there, and the solution is to override the meaning of some system-level primitives, such as SetTimeout and Promise. The only headache above was the async statement, which meant that the body of the function could be effectively reordered. Asyncs are ultimately triggered by promises, so you'll have to override your Promise with something that is context aware. It's quite involved, and because my use case is outside of browser or even node, I won't bore you with details. For most people hitting this kind of problem - just use zone.js.
Hacky Solution 2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
In this solution, I'm making an object that overrides the getAssignedValue() method, and re-evaluates the source code of the passed function, effectively changing the meaning of this. Still not quite production grade...
Edit.
Oops, this breaks closures.
I don't know typescript so possibly this isn't useful, but what about something like:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
Hacky Solution 1
We actually have a working implementation. It's such a painful hack, but proves that this should be possible. Somehow. Maybe there's even a super simple solution that I'm missing just because I've been staring at this for too long.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
So the idea in our solution is to examine the stack trace of the new Error().stack, and wrap something we can extract as a token, which in turn we'll put into a repo. Hacky? Very hacky.
Notes
Testing shows that this is actually quite workable, but requires a more modern execution environment than we have - i.e. ES2017+.
I have 3 classes, all extend the previous one.
Entity -> Body -> Player
Each one has a die() method which do very different things.
Entity.die() will call the db
Body.die() will animate the body
Player.die() will call the UI and play special sound.
I don't want to manually call Entity.die() inside Body.die method, mainly because I have many classes and many common methods and I don't want to forget something.
I wrote this little piece of code which does exactly this, the Error stack is easy to understand and points to the correct lines.
function overLoadMethods (parent, children) {
const methods = {}
for (let [fname, fn] of Object.entries(parent)) {
if (typeof fn === 'function') {
if (children[fname]) {
methods[fname] = function () {
fn()
children[fname]()
}
Object.defineProperty(methods[fname], 'name', { value: fname })
} else {
methods[fname] = fn
}
}
}
return methods
}
function createEntity () {
return {
die: () => {
console.log(new Error().stack)
console.log('entity die')
}
}
}
const bodyMethods = {
die: () => {
console.log(new Error().stack)
console.log('body die')
}
}
function createBody () {
const entity = createEntity()
const overLoadedMethods = overLoadMethods(entity, bodyMethods)
return {
...entity,
...bodyMethods,
...overLoadedMethods
}
}
const playerMethods = {
die: () => {
console.log(new Error().stack)
console.log('player die')
}
}
function createPlayer () {
const body = createBody()
const overLoadedMethods = overLoadMethods(body, playerMethods)
return {
...body,
...playerMethods,
...overLoadedMethods
}
}
const player = createPlayer()
// will call Entity.die() then Body.die() then Player.die()
player.die()
Everything is working fine but I never saw this pattern before and I guess there is a good reason which I'm unaware of.
Could someone point the weakness of this pattern if there is one (pretty sure there is) ?
Common Lisp has something similar. When you define a method in a derived class you can decide whether this method should be executed:
:before (i.e. the base method will be called automatically after specialized one)
:after (i.e. the base method will be called automatically before the specialized one)
:around (i.e. only the specialized method will be called, but inside its body you can call the base method with call-next-method that is a special syntax that allows calling base method with either the parameters specified by the caller or the parameters that you want to pass instead).
For example C++ only has around available for general methods (but without the ability to call the base version with original parameters) and forces instead use of before in constructor and after in destructors.
I understand the desire to not repeat code and create code that makes it hard to make mistakes and forget things. But you still have code the you need to remember to wire up. For example, instead of calling Entity.die() you need to call overLoadMethods(). I'm not sure that's an improvement over regular of classes and calling super.die().
You can get the chained method behavior using ES6 classes (you can also get it using prototypes). This has a lot of advantages:
• The pattern is baked into the language.
• It's very clear to see parent/child relationship
• There's a lot of commentary, theory, and examples of different patterns
class Entity {
die() {
// Entity-specific behavior
console.log('entity die')
}
}
class Body extends Entity {
die() {
super.die()
// Body-specific behavior
console.log('body die')
}
}
class Player extends Body {
die() {
super.die()
// Player-specific behavior
console.log('player die')
}
}
const player = new Player
// will call Entity.die() then Body.die() then Player.die()
player.die()
I have a lot of functions like the following:
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
I have an object with I would like to use to fill in the arguments of the function
var obj = {liked: 'apple', disliked: 'pear'}
How can I run the function using the object to specify the arguments?
I tried using spread syntax:
runme(...obj)
But this produces:
TypeError: Found non-callable ##iterator
How can I run a function with parameters from an object?
I can't change the functions, as I am creating a wrapper that needs to be able to handle arbitrary functions.
Edit: I've edited the post to use 'liked' and 'disliked' instead of 'one' and 'two' as this better shows that ordering matters.
I can use any version of JavaScript, up to and including ES9.
There is no general-purpose way to accomplish this.
If you can guarantee the provenance of the source then you could use an AST operation to build your wrappers during a transpilation or load phase.
If you cannot, then you're particularly out of luck, because the parameter names may be mangled, making an object-key-to-parameter-name transformation impossible.
I can't change the functions, as I am creating a wrapper that needs to be able to handle arbitrary functions.
That's unfortunate, since making them accept a destructured parameter would be exactly what you need.
Unfortunately, the names of parameters are not available unless you parse the result of calling toString on the function, which is...fraught with peril. You basically need a full JavaScript parser (because parameter lists are complex these days, including possibly containing entire function definitions for default values) and minifiers and such may rename parameters (changing liked to _0, for instance).
You also can't count on the order of the properties in an object. (They do have an order, but not one that helps here...or almost anywhere else.)
You've said you need to handle functions whose parameters you don't know in advance, so my various ideas around wrapping functions with utilities that require passing in the names of the parameters won't work. (If anyone's curious, look at the revision list to see those.)
You can do this from toString if we can make several assumptions:
The function parameter lists are simple. They don't include destructuring or default values.
Comments are not used within the parameter lists.
Your minifier does not rename function parameters.
The functions are all traditional functions, methods, or arrow functions that do have () around the parameter list (so for instance, (x) => x * 2, not just x => x * 2).
You don't mind that it'll be fairly inefficient (parsing each time).
That's a lot of assumptions and I don't recommend it. But if you can rely on them:
// LOTS of assumptions here!
function run(f, obj) {
let params = /\(([\w\s,]*)\)/.exec(String(f));
if (!params) {
throw new Error("Couldn't parse function");
}
params = params[1].split(/\s*,\s*/).map(n => n.trim());
return f.apply(this, params.map(param => obj[param]));
}
run(runme, obj);
Live Example:
// Traditional function
const runme = function(liked, disliked){
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Traditional function with newlines
const runme2 = function(
liked,
disliked
){
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Arrow function
const runme3 = (liked, disliked) => {
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Method
const {runme4} = {
runme4(liked, disliked) {
console.log(`i like ${liked} but I hate ${disliked}`)
}
};
const obj = {liked: 'apple', disliked: 'pear'}
function run(f, obj) {
let params = /\(([\w\s,]*)\)/.exec(String(f));
if (!params) {
throw new Error("Couldn't parse function");
}
params = params[1].split(/\s*,\s*/).map(n => n.trim());
return f.apply(this, params.map(param => obj[param]));
}
run(runme, obj);
run(runme2, obj);
run(runme3, obj);
run(runme4, obj);
That works because Function.prototype.toString is standardized now, and even in resource-constrained environments it's required to include the parameter list (but may well not include the rest of the function implementation).
Answer Re-written to factor in correct ordering.
So long as your object keys are named to match the paramaters, you can parse the functions like the following: You can see that no matter which order they are passed in, the output is correct;
var obj = {liked: 'apple', disliked: 'pear'}
var runme = function (liked, disliked) {
console.log(`i like ${liked} but I dislike ${disliked}`)
}
var translateFunc = function (func, args) {
let funcAsString = func.toString();
let argNames = funcAsString.slice(funcAsString.indexOf('(') + 1, funcAsString.indexOf(')')).match(/([^\s,]+)/g);
let parsedArgs = [];
for (let a of argNames) {
for (let k of Object.keys(args)) {
if (k == a) {
parsedArgs.push(args[a]);
}
}
}
eval(func(...parsedArgs));
}
translateFunc(runme, obj);
obj = {disliked: 'pear', liked: 'apple'}
translateFunc(runme, obj);
A bit late, but here's another try. I think it works, but minification of the code will change your function arguments but not the object properties, so you'll need to work around that.
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
var obj = {liked: 'apple', disliked: 'pear'}
const wrap = runme => {
const regex = new RegExp(/\(([^\)]*)\)/)
const args = regex.exec(runme.toString())[1].split(',').map(s => s.trim())
return obj => {
const result = args.map(a => obj[a])
runme(...result)
}
}
const wrappedRunme = wrap(runme);
console.log(wrappedRunme(obj))
I got the same error and just in case this was the issue, here's what resolved it for me...
Instead of:
runme(...obj)
I needed to spread the object into a new object:
runme({ ...obj })
You can do that in following steps:
First convert the function to a string
Use RegExp to get the arguments of the function.
Use split() to convert the argument string to array of arguments.
Then use reduce() on that array and create a new ordered array having values of given object.
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
function wrapper(obj){
let args = runme.toString().match(/\(.+\)/)[0]
args = args.slice(1,-1);
args = args.split(',').map(x => x.trim());
let ordered = args.reduce((ac,ar) => [...ac,obj[ar]],[]);
runme(...ordered);
}
wrapper({liked:"liked", disliked:"disliked"})
wrapper({ disliked:"disliked",liked:"liked"})
The real code is larger, so I won't post it. It looks pretty much like this:
class A {
process(source) {
// I perform several operations with array helper functions here:
const filtered = source.filter(item => item);
const condition = filtered.some(item => item);
if (condition) {
const mapped = source.map(item => /* Mapping operations... */);
const sorted = mapped.sort((a, b) => { /* Some sort conditions... */ });
return sorted;
} else {
const mapped2 = filtered.map(item => /* A different mapping operation... */);
return mapped2;
}
}
}
const a = new A();
while (true) {
const source = getSourceFromSomewhere(); // Array (40 - 50 items aprox)
const b = a.process(source);
// ...
}
The problem: Basically, performance; "Don't make functions within a loop".
On every iteration a bunch of anonymous functions are getting created.
My solution:
class A {
// Predefine it:
sort() { /* Sort logic */ }
map() { /* Map logic */ }
map2() { /* Map logic */ }
filter() { /* Filter logic */ }
some() { /* Condition */ }
process(source) {
const filtered = source.filter(this.filter); // Note: Scope of 'this' is changed.
const condition = filtered.some(this.some);
if (condition) {
const mapped = source.map(this.map);
const sorted = mapped.sort(this.sort);
return sorted;
} else {
const mapped2 = filtered.map(this.map2);
return mapped2;
}
}
}
Another problem: Some of this functions need access to properties of the object itself, but the scope of this has been changed.
It's worth to call .bind(this) instead of creating the anonymous function? or pretty much the same?
What would you do in my case?
Thanks in advance.
To initialize bound functions within a class you could do
class Test {
fn = (t) => this[t]
}
basically the same what you wanted to do anyways.
The problem: Basically, performance; "Don't make functions within a loop".
Your premise is incorrect.
JavaScript engines are highly optimized. They do not laboriously read the source text character-by-character each time through a loop, or each time a function is called, much less each time a callback is invoked. They scan, parse, and pre-compile. At worst, functions like item => item will be created only once per function invocation. More likely, they will be pre-created during the initial scanning and parsing process.
Therefore, you don't need to worry about performance when considering whether to pre-define the functions yourself. The guiding principle should instead be program readability and structure.
If you do want to pre-define a function, as long as it does not use this, consider defining it outside the class:
function filterFunc(item) { return item.val < MAX; }
class A {
process() {
const filtered = source.filter(filterFunc);
If you do need 'this`, then in modern JS it is preferable to write
class A {
filterFunc(item) { return item.val < this.MAX; }
process() {
const filtered = source.filter(item => this.filterFunc(item));
instead of worrying about binding this.filterFunc making you write
class A {
constructor () { this.filterFunc = this.filterFunc.bind(this); }
process() {
const filtered = source.filter(this.filterFunc);
While as mentioned in another answer
class Test {
// constructor etc.
step = x => x + this.currentStep;
process() {
return this.arr.map(step);
}
}
would be a concise way to achieve your intended behavior, as this is already bound to the instance, it requires public class fields which is still in Stage 2, and therefore not yet supported in many browsers without a transpiler.
It is good to remember that you can always pass the this scope to the second argument of functions such as map and filter, so you don't have to manually bind your functions beforehand. The code then becomes
class Test {
// constructor etc.
step(x) { return x + this.currentStep; }
process() {
return this.arr.map(step, this);
}
}
This is very close to the solution you have in mind while making sure your functions have the correct scope.
Though I don't know much about inner workings of browsers I think if the code is hot enough (that is being ran often), the optimized compiler might not need to recreate those anonymous functions every run.