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.
It 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.