Функторы в JavaScript

Скорее всего вы уже использовали фанкторы, это map и filter. Сегодня я попробую разобраться что именно в map и filter заставляет отнести их к фанкторам. Также я попробую написать фаннктор. На для начала я напишу программу без использования фанкторов чтобы увидеть и понять проблему, которую фанкторы решают. Рассмотрим довольно бессмысленную функцию plus1:


function plus1(value) {
return value + 1;
}

console.log(plus1(3)) // 4

Настолько простая функция взята спецально, чтобы не отвлекатья от идеи, которую я хочу понять. Просто представьте что в реальной жизни эта функция делает что-то гораздо сложнее чем просто прибавляет 1 к полученному значению.

Функция отлично обрабатывает переданные ей числа, но что будет если я передам ей массив [3,4]?Очевидно что она не сможет его обработать. Но я бы хотел получить назад массив [4,5], так что я перепишу plus1 вот так:


function plus1(value) {
  if (Array.isArray(value)) {
    var newArray = [];
    for(var i=0; i < value.length; i++){
      newArray[i] = value[i] + 1;
    }
    return newArray;
  }
  return value + 1;
}

console.log(plus1[3, 4]) // [4, 5]

Код говорит сам за себя, но на всякий случай я поясню что он делает. Он проверяет переданный аргумент и если это массив, то создаётся новый пустой массив и производится обход массива-аргумента, каждый элемент которого увеличивается на 1 и присваивается новому под тем же индексом под каким был прочитан (i). Если же аргумент не массив, то просто увеличиваю его на 1 и возвращаю. И это работает, если я передам ему [3, 4] то он вернёт [4, 5].

А что если я хочу чтобы моя функция plus1 работала еще и со строками? К примеру я хочу передать ей 'ABC' а получить 'BCD'. Придётся доработать функцию чтобы она смогла обрабатывать и строки:


function plus1(value) {
  if (Array.isArray(value)) {
    var newArray = [];
    for(var i=0; i++; value.length; i++){
      newArray[i] = value[i] + 1;
    }
    return newArray;
  }
  if (typeof value === 'string') {
    var chars = value.split('');
    var newCharArray = [];
    for(var i=0; i < chars.length; i++){
      newArray[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1);
    }
    return newCharArray.join('');
  }
  return value + 1;
}

console.log(plus1([3, 4])) // [4, 5]

Как видно добавленный код обрабатывает строки, разбивая их на массив символов, формируя на основе его новый массив, прибавляя к коду символа 1. Но не столько важно что делает этот код сколько то, насколько моя простая функция plus1 разрослась. Все эти проверки, итерации по массиву, это уже гораздо больше чем просто "плюс 1". Но это работает.

А что если я захочу написать функцию "минус 1"? Мне придётся продублировать много кода заменив лишь малую его часть. А что если дальше я захочу сделать функцию "плюс 10" или "умножить на 5". Очевидно тут можно вынести дублирующий код который  просто перебирает элементы коллекции. Вот тут и появляются фанкторы. В JavaScript уже есть встроенные, и очень вероятно уже знакомые фанкторы - map и filter. Сейчас я попробую разобраться что именно относит некоторые функции к категории фанкторов. Но для начала я попробую создать свой собственный фанктор, аналогичный map но работающий со строками:


function stringFunctor(value, fn) {
  var chars = value.split('');
  return chars.map(function(char){
    return String.fromCharCode(fn(char.charCodeAt(0)))
  }).join('');
}

function plus1(value) {
  return value + 1;
}

function minus1(value) {
  return value - 1;
}

[3, 4].map(plus1);
stringFunctor('ABC', plus1); // 'BCD'
stringFunctor('XYZ', minus1); // 'WXY'
Функция stringFunctor содержит ту же самую логику что была реализована в большой функции plus1 выше (в той её части которая обрабатывала строки). Как тут можно видеть stringFunctor принимает значение value и функцию fn. В первом запуске функции value = 'ABC' а fn это функция plus1. Функция stringFunctor забивает value на массив ['A', 'B', 'C'] символов и применяет уже многим знакомую функцию map, в качестве функции параметра к map мы передаём безымянную функцию которая каждый символ массива chars преобразует к коду этого символа, дальше она передаёт этот код в функцию fn и преобразует возвращённый результат назад в символ. Массив-результат работы map мы соединяем методом join назад в строку и возвращаем эту строку.

Поскольку stringFunctor по-сути распаковывает строку и преобразует её в коды символов в качестве функции аргумента fn мы можем передать любую функцию которая ожидает число на вход: plus1, minus1 и.т.д. Теперь функции plus1, minus1 не должны содержать в себе эту перебирающую (распаковывающую) логику как в первых примерах потому как эта часть работы переложена на фанктор. И стандартный map и мой stringFunctor - это фанкторы; функции, которые принимаю данные и функцию, данные могут быть массивом в случае map или строкой в случае с stringFunctor. Фанктор получает составные данные, такие как массив или строка, берёт каждый элемент этих данных, передаёт функции обработчику получает от неё результат и формирует новые данные из этих результатов.

Другой пример фанктора это стандартный метод массива filter. Метод forEach фанктором не является, хоть он и разворачивает структуру, передаёт элементы функции-обработчику но он не возвращает результат, поэтому фанктором называться не может.
Подведём итог. В контексте JavaScript фанктор это функция которая получая составные данные и функцию(функцию-обработчик) производит распаковку этих данных на отдельные элементы, передаёт их функции-обработчику и на основе полученных данных формирует новую структуру данных и возвращает её.

Это всего лишь перевод и текстовая версия вот этого замечательного видео

Комментарии

  1. Интересная статья. Благодарю.

    ОтветитьУдалить
  2. Online Casino NJ | Online Casino | Get a $500 Bonus
    The 더킹카지노 first online casino NJ will require you to login to your account 제왕카지노 to get your free $500 bonus and you 카지노사이트 will be welcome!

    ОтветитьУдалить

Отправить комментарий

Популярные сообщения из этого блога

Загрузка CPU на Cisco Catalyst 4500 и Cat4k Mgmt LoPri

Пользовательские параметры в Zabbix (UserParameter)