javascriptconstructorlodashantlr4higher-order-functions

Passing a constructer as a parameter as if it were a regular function in JavaScript?


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.


Solution

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