I am using ANTLR 4, and it is common to instantiate a class and immediately pass it as the argument of the next class' constructor, as per the docs for the ANTLR 4 JavaScript target:
import antlr4 from 'antlr4';
import MyGrammarLexer from './QueryLexer.js';
import MyGrammarParser from './QueryParser.js';
import MyGrammarListener from './QueryListener.js';
const input = "field = 123 AND items in (1,2,3)"
const chars = new antlr4.InputStream(input);
const lexer = new MyGrammarLexer(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new MyGrammarParser(tokens);
const tree = parser.MyQuery();
Clearly this is a situation suited for function composition, and I am using Lodash (FP).
At first I tried the following:
// imports...
import _ from "lodash/fp.js";
const parse = _pose([
_.invoke("MyQuery"),
MyGrammarParser,
antlr4.CommonTokenStream,
MyGrammarLexer,
antlr4.InputStream,
]);
But this fails with TypeError: Class constructor ke cannot be invoked without 'new'
.
Inserting new
on each line inside _pose
(other than _.invoke
) fails with TypeError: Cannot read properties of undefined (reading 'length')
at new antlr4.InputStream
.
Instead, I can use a helper function:
// imports...
import _ from "lodash/fp.js";
_.construct = Constructor => param => new Constructor(param);
const parse = _pose([
_.invoke("MyQuery"),
_.construct(MyGrammarParser),
_.construct(antlr4.CommonTokenStream),
_.construct(MyGrammarLexer),
_.construct(antlr4.InputStream),
]);
const tree = parse("field = 123 AND items in (1,2,3)");
This works, and IMO looks way better than the original, but I want to know if there is a way to write this using built-in Javascript or Lodash functions. As far as I know, it is not possible to pass a constructor as a parameter as if it were a regular function, hence _.construct
.
In summary, I want to pass a constructor as a parameter as if it were a regular function without having to first wrap it in a helper function.
For those unfamiliar with Lodash, see the Lodash Documentation and the Lodash FP Guide, however the solution to this question should be Lodash/ANTLR 4 independent.
I am using ANTLR 4, and it is common to instantiate a class and immediately pass it as the argument of the next class' constructor, as per the docs for the ANTLR 4 JavaScript target:
import antlr4 from 'antlr4';
import MyGrammarLexer from './QueryLexer.js';
import MyGrammarParser from './QueryParser.js';
import MyGrammarListener from './QueryListener.js';
const input = "field = 123 AND items in (1,2,3)"
const chars = new antlr4.InputStream(input);
const lexer = new MyGrammarLexer(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new MyGrammarParser(tokens);
const tree = parser.MyQuery();
Clearly this is a situation suited for function composition, and I am using Lodash (FP).
At first I tried the following:
// imports...
import _ from "lodash/fp.js";
const parse = _.compose([
_.invoke("MyQuery"),
MyGrammarParser,
antlr4.CommonTokenStream,
MyGrammarLexer,
antlr4.InputStream,
]);
But this fails with TypeError: Class constructor ke cannot be invoked without 'new'
.
Inserting new
on each line inside _.compose
(other than _.invoke
) fails with TypeError: Cannot read properties of undefined (reading 'length')
at new antlr4.InputStream
.
Instead, I can use a helper function:
// imports...
import _ from "lodash/fp.js";
_.construct = Constructor => param => new Constructor(param);
const parse = _.compose([
_.invoke("MyQuery"),
_.construct(MyGrammarParser),
_.construct(antlr4.CommonTokenStream),
_.construct(MyGrammarLexer),
_.construct(antlr4.InputStream),
]);
const tree = parse("field = 123 AND items in (1,2,3)");
This works, and IMO looks way better than the original, but I want to know if there is a way to write this using built-in Javascript or Lodash functions. As far as I know, it is not possible to pass a constructor as a parameter as if it were a regular function, hence _.construct
.
In summary, I want to pass a constructor as a parameter as if it were a regular function without having to first wrap it in a helper function.
For those unfamiliar with Lodash, see the Lodash Documentation and the Lodash FP Guide, however the solution to this question should be Lodash/ANTLR 4 independent.
Share Improve this question asked 2 days ago James BawJames Baw 433 bronze badges 5 |1 Answer
Reset to default 0It is possible to invoke a constructor without new
using Reflect.construct
:
class Example {
hello = "world"
}
//pass an empty array for the constructor arguments
const obj = Reflect.construct(Example, []);
console.log( obj instanceof Example );
console.log( obj.hello );
To make this point-free using vanilla JS only, there is Function#bind
:
class Example {
hello = "world"
}
const myConstruct = Reflect.construct.bind(null, Example, []);
const obj = myConstruct();
console.log( obj instanceof Example );
console.log( obj.hello );
However, Lodash provides partial()
which can be used with placeholders and can be more convenient:
class Example1 {
hello = "world"
}
class Example2 {
hello = "Fred"
}
const myConstruct = _.partial(Reflect.construct, _, []);
// placeholder for a constructor ----------------^
const obj1 = myConstruct(Example1);
const obj2 = myConstruct(Example2);
console.log( obj1 instanceof Example1 );
console.log( obj1.hello );
console.log( obj2 instanceof Example2 );
console.log( obj2.hello );
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-fp/0.10.4/lodash-fp.min.js"></script>
However, I personally feel it is not really worth doing that. In the end, it ends up throwing more library code at a task that where a simple function is enough:
const construct = (constructor) => (...args) =>
new constructor(...args); // or Reflect.construct(constructor, args)
which is essentially the solution you already came up with.
class
syntax can only be called as constructors, so Lodash would need separate APIs for dealing with them, or else something like what you've done here. – Pointy Commented 2 days ago_.construct
": That's clearly not true, since you're successfully passing a constructor as a parameter to_.construct
. What you really mean is "As far as I know, if a function expects its argument to be a regular function rather than a constructor, it is not possible to pass a constructor and have the function create the desired object." Which is true. But there's really nothing wrong with your current approach, namely, wrapping thenew
expression in a regular function. – ruakh Commented 2 days agonew
. – Sergey A Kryukov Commented yesterday