JS 回调:延续传递还是糖果工厂风格?

JS Callbacks: continuation-passing or candy factory style?

在编程风格课程中,我们被要求在“持续传递风格”和“糖果工厂风格中实现一些代码”。

我们正在阅读的这本书 "Exercises in programming styles" 作者是 Cristina Videira Lopes(第 5 章和第 8 章)。

我们被要求用另一种语言实现书中的示例代码(书中是Python,现在我正在使用Javascript)。

为了理解我的问题,我将向您展示书中显示的两个主要区别:

糖果工厂风格

#!/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])

延续-传递风格

#!/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)
                        })
                    })
                })
            })
        })
    })
}

根据我的理解和书中的例子,上面Javascript中写的是Continuation passing,因为函数是作为参数传递的。但与此同时,为了调用主函数,您使用与糖果工厂相同的调用 "in pipeline style"。

上面用JS写的代码是怎么实现糖果工厂风格的?该代码(基于回调)是糖果工厂还是延续传递风格?如何在不使用回调的情况下编写上面的代码,同时信任 JS?

我认为你混淆了两件事。糖果风格通常被称为功能组合。这就是一个函数的输出是下一个函数的输入的地方。

f(g(h(1)))

h(1) 输出一个值,这是 g 的输入,它输出一个值,是 f 的输入。

这与 Javascript 中用于异步操作的回调样式不同。

f(1,g)

其中 f 获取一个值,对其进行处理并稍后调用 g

通常在 JavaScript 中,您需要处理异步操作,但在这些情况下您只需要回调(延续)。像你的 stringToLower 这样的函数只需要 return 数据。

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

如果您要调整代码以遵循这些规则,那么您可以做一些更熟悉的事情:

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

知道这是组合,我们可以使用另一个函数来进一步简化它。

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

我们可以这样使用它:

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

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

现在这可能看起来很陌生,但让我们来看看吧。函数 compose 接受一个名为 fns 的参数列表。 ...(其余运算符)只接受单个参数并将它们放入数组中。您会注意到 compose 函数 return 是另一个函数。所以 compose(omg) 会 return 另一个等待 value 的函数。当您提供该值时,该函数将关闭 运行。我们用函数列表调用 compose,它 return 是一个等待值的函数。我们将该函数分配给 const processFile。然后我们进行异步操作并将 processFile 设置为回调。回调(我们的 compose 函数)接收它正在等待的值,然后同步进行所有处理。

希望这能解决一些问题。我还建议您查看 promises,这样您就不必处理回调。

JavaScript 很有趣,因为它本身就是一种异步语言。 Python 另一方面不是。这意味着在 python 中您可以同时使用两种样式,但在 Javascript 中有时您必须同时使用这两种样式。

另外,请记住,在 JavaScript 中,有回调,我们用它来构建承诺,我们用它来构建 Async/Await。了解回调流程对于能够更有效地使用更高级别的工具至关重要。