JS Callbacks: continuation-passing or candy factory style?

210 Views Asked by At

At programming styles course we are asked to implement some code in both, "continuation-passing style" and "candy factory style".

The book we are reading is "Exercises in programming styles" by Cristina Videira Lopes (chapter 5 and 8).

We are asked to implements the examples codes of the book in another language(in the book is in Python, now I am using Javascript).

In order to understand my problem, i will show you the two main differences showed in the book:

Candy Factory Style

#!/usr/bin/env python
read_file(path_to_file):
    """
    Takes a path to a file and returns the entire contents of the 
    file as a string
    """
    with open(path_to_file) as f:
        data = f.read()  
   return data


def filter_chars_and_normalize(str_data):
     ...

.
.
.

print_all(sort(frequencies(remove_stop_words(scan(
filter_chars_and_normalize(read_file(sys.argv[1]))))))[0:25])

Continuation-Passing Style

#!/usr/bin/env python

def read_file(path_to_file, func): 
    with open(path_to_file) as f:
       data = f.read()
    func(data, normalize)


def filter_chars(str_data, func):
    pattern = re.compile(’[\W_]+’)
    func(pattern.sub(’ ’, str_data), scan)


. 
. 
. 


read_file(sys.argv[1], filter_chars)

Javascript

const fs = require('fs');


var myArgs = process.argv.slice(2);

function read_file(path_to_file,callback){
    fs.readFile(path_to_file, "utf-8",(err, data) => {
        if (err) throw err;
        callback(data);
      });
}

function string_to_lower(str_data,callback){
    var data = str_data.toLowerCase()
    callback(data)
}

.
.
.

function TermFrequency(){
    read_file(myArgs[0],function(result){
        string_to_lower(result, function(result2){
            remove_non_alphanumeric(result2,function(result3){
                remove_stop_words(result3,function(result4){
                    frequencies(result4,function(result5,result6){
                            sort(result5,result6,function(result7,result8){
                            write_out(result7,result8)
                        })
                    })
                })
            })
        })
    })
}

From what I understood, and from the examples from the book, what's written above in Javascript, is Continuation passing, since a function is passed as parameter. But at the same time, in order to call the main function you use the same call "in pipeline style" as the candy factory.

How is it possible to achieve the candy factory style given the code written in JS above? Is that code (which is based on callbacks) candy factory or continuation-passing style? How can I write the code above, without making use of callbacks and at the same time trusting JS?

1

There are 1 best solutions below

1
On BEST ANSWER

I think you are confusing 2 things. Candy style is more commonly referred to as function composition. That is where the output of one function is the input for the next one.

f(g(h(1)))

h(1) outputs a value and that is the input for g which outputs a value and is the input for f.

that is different that the callback style used in Javascript for asynchronous operations.

f(1,g)

Where f takes a value, processes it and calls g at a later time.

Often in JavaScript you'll need to handle asynchronous operations but you only need callbacks(continuations) in those situations. Functions like your stringToLower only need to return data.

function string_to_lower (str) {
  return str.toLowerCase();
}

If you were to adjust your code to follow those rules, then you could do something more familiar:

function TermFrequency(){
  read_file(myArgs[0],function(result){
   write_out( sort(frequencies(remove_stop_words(remove_non_alphanumeric(string_to_lower(result))))));
  }
}

Knowing that this is composition we could use another function to simplify this even more.

function compose (...fns) {
  return function (value) {
    fns.reduce(function (result, fn) {
      return fn(result);
    }, value);
  }
}

and we can use it like this:

const processFile = compose(
  string_to_lower,
  remove_non_alphanumeric,
  remove_stop_words,
  frequencies,
  sort,
  write_out,
);

function TermFrequency(){
  read_file(myArgs[0], processFile);
}

Now this may look foreign but let's walk through it. The function compose takes a list of arguments called fns. The ... (the rest operator) just takes individual arguments and puts them into an array. You'll notice that the compose function returns another function. So compose(omg) would return another function waiting for a value. When you provide the value then the function goes off running. We call compose with a list of functions and it returns a function waiting for a value. We assign that function to the const processFile. Then we do our async operation and set processFile as the callback. The callback (our compose function) receives the value it's waiting for and then does all the processing synchronously.

Hope this clears a few things up. I'd also recommend looking into promises so you don't have to deal with callbacks.

JavaScript is interesting because it is natively an asynchronous language. Python on the other hand is not. That means in python you can us eboth styles but in Javascript there are times when you must use both styles.

Also, keep in mind that, in JavaScript, there are callbacks, with which we built promises, with which we built Async/Await. Understanding the callback flow is imperative to being able to use the highler level tools more effectively.