Обратные вызовы JS: передача продолжения или стиль конфетной фабрики?

На курсе по стилям программирования нас просят реализовать некоторый код как в «стиле передачи продолжения», так и в «стиле конфетной фабрики».

Мы читаем книгу «Упражнения по стилям программирования» Кристины Видейры Лопес (главы 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, так как функция передается в качестве параметра. Но в то же время, чтобы вызвать основную функцию, вы используете тот же вызов «в конвейерном стиле», что и конфетная фабрика.

Как можно добиться стиля конфетной фабрики с помощью кода, написанного на JS выше? Является ли этот код (который основан на обратных вызовах) конфетной фабрикой или стилем передачи продолжения? Как я могу написать приведенный выше код, не используя обратные вызовы и в то же время доверяя JS?


person BioShock    schedule 24.09.2018    source источник
comment
Я думаю, вы обнаружите, что стиль конфетной фабрики обычно не используется. Я не уверен, что это должно означать. Однако функция TermFrequency, которую вы описываете, определенно относится к стилю передачи продолжения. Если ваши служебные функции проще, так что string_to-lower, например, просто принимает строку и возвращает ее версию в нижнем регистре, то вы можете легко переписать ее, чтобы она была заметно проще с помощью промисов или async/await. Вы не можете избавиться от лежащей в основе асинхронности. Если вы включаете асинхронную функцию read_file, все, что ее касается, также должно быть асинхронным.   -  person Scott Sauyet    schedule 24.09.2018


Ответы (1)


Я думаю, вы путаете 2 вещи. Конфетный стиль чаще называют функциональной композицией. Это когда результат одной функции является входом для следующей.

f(g(h(1)))

h(1) выводит значение, и это вход для g, который выводит значение и является входом для f.

это отличается от стиля обратного вызова, используемого в Javascript для асинхронных операций.

f(1,g)

Где f принимает значение, обрабатывает его и вызывает g позднее.

Часто в JavaScript вам нужно обрабатывать асинхронные операции, но в таких ситуациях вам нужны только обратные вызовы (продолжения). Такие функции, как ваша stringToLower, должны возвращать только данные.

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 возвращает другую функцию. Таким образом, compose(omg) вернет другую функцию, ожидающую value. Когда вы предоставляете значение, функция отключается. Мы вызываем compose со списком функций, и он возвращает функцию, ожидающую значения. Мы назначаем эту функцию const processFile. Затем мы выполняем нашу асинхронную операцию и устанавливаем processFile в качестве обратного вызова. Обратный вызов (наша функция compose) получает ожидаемое значение, а затем выполняет всю обработку синхронно.

Надеюсь, это прояснит некоторые вещи. Я бы также рекомендовал изучить промисы, чтобы вам не приходилось иметь дело с обратными вызовами.

JavaScript интересен тем, что изначально является асинхронным языком. Python, с другой стороны, нет. Это означает, что в Python вы можете использовать оба стиля, но в Javascript бывают случаи, когда вы должны использовать оба стиля.

Кроме того, имейте в виду, что в JavaScript есть обратные вызовы, с помощью которых мы создавали промисы, с помощью которых мы создавали Async/Await. Понимание потока обратного вызова необходимо для более эффективного использования инструментов более высокого уровня.

person ktilcu    schedule 24.09.2018
comment
Большое спасибо за ответ. Я собираюсь изучить немного больше в ближайшие часы. Тем не менее, очень полезное объяснение - person BioShock; 24.09.2018