4

I'm writing a file processor in JavaScript (Node.js). Simplified pseudo code as below. For each line in file it prints to output and if the line is an include indicator it recursively handle the included file.

function fileProcessor (file) {
  // read lines from file
  lines.forEach( function (line) {
    // #1 - include processor:
    if line matches "#include includefile"
      fileProcessor(includefile);
    // #2 - output processor:
    // write line to output
  });
}

The next step I want to improve its design by decoupling the file processor and line processor. I'm familiar with OO, it should look like below class diagram. But now I want to try it in FP way.

Define a function that returns the fileProcessor, which line processors are applied.

var fileProcessorGen = function (lineProcessors) {
  return function (file) {
    // read lines from file
    lines.forEach( function (line) {
      lineProcessors.forEach( function (processor) {
        processor.call(this, line);
      });
    });
  };
};

My purpose is to achieve separation of concerns. In the example there are only two line processors but actually there will be more. And the caller will pick some line processors into use on its purpose. So I don't hard code them into file processor or any line processors.

var fileProcessor = fileProcessorGen([includeProcessor, outputProcessor]);

Now my problem is how can I call fileProcessor (which is partial application) inside my include processor?

function includeProcessor(line) {
  if line matches "#include includefile"
    fileProcessor(includefile);  // <--- how to get fileProcessor ?
};

function outputProcessor(line) {
  // write line to output
}

Maybe I have to pass the fileProcessor as argument into includeProcessor, but how?

function includeProcessor(line, fileProcessor) {
  if line matches "#include includefile"
    fileProcessor(includefile);
};

var fileProcessorGen = function (lineProcessors) {
  return function (file) {
    // read lines from file
    lines.forEach( function (line) {
      lineProcessors.forEach( function (processor) {
        processor.call(this, line, ???);  // <-- how to pass the parameter?
      });
    });
  };
};

2 Answers 2

1

You just have to give the anonymous function a name then you can refer to it by that identifier from within the scope of the function, no matter what identifier you assign that function to. ( I would almost always recommend giving functions names, even function expressions. Not just to be able to refer to them but to make it easier to read stack traces ).

function fileProcessorFactory (lineProcessors) {
  return function fileProcessor (file) {
    // read lines from file
    lines.forEach( function (line) {
      lineProcessors.forEach( function (processor) {
        processor.call(this, line, fileProcessor );
      });
    });
  };
};

var fileProcessorInstance = fileProcessorFactory( [ includeProcessor, outputProcessor ] );

But you should of course be aware that your current solution is extremely inefficient as it creates whole new sets of functions every time it's called and you could make this a lot simpler and more efficient if you would just declare the functions once and let them call each other.

function fileProcessor ( file ) {
    //split those lines
    lines.forEach( lineProcessor );
}

function lineProcessor ( line ) {
    if line matches "#include includefile"
        fileProcessor( includefile );
    else
        outputProcessor( line );
}

function outputProcessor ( line ) {
   //...
}

If you want to support multiple directives, just look them up by their name in a hashmap aka "a javascript object" or throw an Error if the name isn't in the map. ( there's also the actual Map Datatype in ES6 you can also use for this ).

var directives = {
     include : function includeDirective ( expression ) {
     },
     define : function defineDirective ( expression ) {
     },
     //...
};

You also can do this completely without regular expressions because preprocessors usually only follow 3 super simple rules ( in pseudocode )

  1. if first letter "#" = is directive
  2. while not space = name of directive
  3. while not newline = expression

To clarify the function creation issue. The functional nature of javascript seperates functions into function declarations and function expressions. Function declarations are evaluated once, function expressions are evaluated every time the control reaches a line containing it resulting in a new function which you also can store in a variable or not. If you don't store it it becomes garbage (for example after the function you passed it to returns).

lines.forEach( function (line) { // creates one new function
  lineProcessors.forEach( function (processor) { //creates one new function per line
    processor.call(this, line);
  });// one function per line is now garbage
}); // the outer function is now garbage
Sign up to request clarification or add additional context in comments.

3 Comments

My purpose is to achieve separation of concerns. In the example there are only two line processors but actually there will be more. And the caller will pick some line processors into use on its purpose.
BTW, I wonder in my solution are the line processor functions really created every time it's called? I think the functions are created once by declaration, and the references are passed in the array. I'm new to JavaScript, please fix me if I'm wrong.
@aleung I hope I could clarify that a bit. You are not alone with that confusion. People often start out looking for the fanciest features and then after a while go back to using the simplest features because they are easy to write / read / maintain / not mess up and usually perform a lot better :)
0

You can create a new instance of fileProcessor and then use it inside includeProcessor.

function includeProcessor(line) {
  if line matches "#include includefile"
  { 
    var fp = fileProcessorGen([includeProcessor, outputProcessor]);
    fp(includefile);  
  }
};

1 Comment

Yes it works. But my purpose is to achieve separation of concerns. Include Processor should not know what other processors are used.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.