Я продолжаю думать о том, чтобы писать больше, но чем больше я думаю об этом, тем меньше я это делаю. Итак, сегодня я перестал думать и начал писать.
Отсюда можно только спускаться…
В основном, я не мог придумать, о чем писать. Большинство вещей, которые я знаю, были написаны ДО СМЕРТИ. Но сегодня я понял: все в порядке! Каждый усваивает вещи по-разному (что, кстати, тоже нормально), так что может случиться так, что кто-то чего-то не понимает, потому что это просто не было продемонстрировано или объяснено способом, соответствующим их стилю обучения.
Кроме того, они говорят, что лучший способ закрепить свое знание чего-либо — это записать это или передать другим. Может быть, это поможет кому-то, даже если этот кто-то только я!
Этим утром я думал о композиционной цепочке функций в Javascript — о том, что мы делаем все больше и больше на работе по мере того, как переходим к более функциональному стилю (да, да — я знаю, что это не ДЕЙСТВИТЕЛЬНО функциональное программирование, но это шаг в правильное направление. Обратите внимание, я сказал СТИЛЬ. Мы пока не собираемся использовать полномасштабный Haskell и крутые усы…).
Во всяком случае, я решил изложить свои мысли о старых фаворитах - map
, filter
и reduce
. Это потребовало некоторой мозговой борьбы, чтобы прийти в себя. Я много читал, смотрел много видео (спасибо Professor Frisby), но все никак не заживало. Они несложные, но мой мозг просто не мог с этим справиться по какой-то причине.
Итак, сначала идет Карта:
Map можно вызывать для массива, который затем вызывает переданную ему функцию для каждого элемента в первом массиве.
const addOne = num => num + 1; let nums = [1,2,3,4,5,6,7,8,9]; let jsMap = nums.map(addOne); // [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Вы можете вставить это в свой редактор или IDE и запустить с помощью Node, чтобы убедиться в этом самостоятельно (то же самое со всеми этими примерами).
Также полезно помнить, что это не обязательно должна быть переданная функция (хотя это, вероятно, окажется более полезным в filter
и reduce
):
let nums = [1,2,3,4,5,6,7,8,9]; let jsMap = nums.map(num => num>= 5); // [ false, false, false, false, true, true, true, true, true ]
Важной особенностью map
является то, что исходный массив не изменяется:
const addOne = num => num + 1; let nums = [1,2,3,4,5,6,7,8,9]; let jsMap = nums.map(addOne); // [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ] console.log(nums); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Мне кажется, что это один из главных бонусов функционального программирования — неизменяемость. По сути, это означает, что исходная вещь остается неизменной. Не мутировал. Радиацией или чем-то еще.
Итак, вернемся к моему вступительному гамбиту — чем отличается это объяснение? Я уверен, что вы видели функцию addOne
, написанную триллион раз, если вы пытаетесь разобраться в этих «чистых» функциях. Ну ничего. Уже.
Но теперь я подумал, что напишу функцию map
вручную. Использование старых добрых JavaScript-циклов for
(хотя сейчас они в значительной степени дьявольские). Все понимают for
loop. Если нет, я бы вернулся к чему-то более простому…
Итак, вот (используя нашу функцию addOne
ранее):
const map = (arr, func) => { let results = []; for(let i = 0; i < arr.length; i++) { let result = func(arr[i]); results.push(result); }; // Note that results is returned - // the original array that was passed in is non-radioactive return results; } let ourOwnVerySpecialMapResults = map(nums, addOne); // [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ] // Nums is still unchanged console.log(nums); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Следует отметить пару вещей: исходный массив nums
не стал радиоактивным (или каким-либо образом изменился). Там, где функция addOne
передается нашей map
, передается только имя без вызываемой функции - это сделано в руководстве map
, которое мы написали.
Но главное, что нужно вынести из этого, это то, что это просто цикл for!!
Каждый результат значения массива, передаваемого в функцию, push
ed в новый возвращаемый массив.
Я предполагаю, что следующий вопрос: если это так просто, почему мы используем JS map
? Ну, мы можем легко связать другие функции с концом нашего результата map
ped. Подробнее об этом позже. Часть 2, возможно (если она есть)…
Далее Фильтр:
Как и в случае с map
, filter
на самом деле просто еще один замаскированный цикл for. Он проходит через массив, проверяет каждое значение и передает его в новый массив, если значение соответствует ожиданиям. Нравится:
let nums = [1,2,3,4,5,6,7,8,9]; let jsFilter = nums.filter(num => num > 5); // [ 6, 7, 8, 9 ]
Итак, он проходит через nums
, проверяет каждое значение, чтобы увидеть, больше ли оно пяти:
num => num > 5
И возвращает его в массиве, если он есть. Простые.
Вот он, простой фильтр JS. Как и прежде, давайте напишем свой собственный:
const moreThanFive = num => { if (num > 5) { return num; } } const filter = (arr, check) => { let results = []; for(let i = 0; i < arr.length; i++) { if (check(arr[i])) { results.push(arr[i]); } }; return results; // nums is still non-mutant }; let ourOwnVerySpecialFilterResults = filter(nums, moreThanFive); // [ 6, 7, 8, 9 ]
Как и раньше, это всего лишь цикл for
, но с оператором if
внутри, который управляет тем, что push
ed помещается в массив results
.
Имеет ли это смысл? Ты со мной? Я надеюсь, что это так…
Наконец, Уменьшить:
Так. Reduce
требует две вещи: функцию и «3,5-дюймовую дискету» (или запоминающее устройство по вашему выбору). Он проходит через массив, к которому он вызывается, передает каждое значение функции, а затем сохраняет его на дискету (флэш-накопитель Minions USB). Дискета не обязательно должна быть числом, это может быть массив и т. д. — просто помните, что он будет придерживаться своего типа: если ваша дискета (64-гигабайтная карта памяти Sandisk micro SD) изначально является массивом, результаты функции будет push
ed в этот массив. Если это число, вы можете диктовать, что делать — прибавлять, вычитать и т. д.
let jsReduce = nums.reduce((sum, num) => { if (moreThanFive(num)) { return sum + num; } else { return sum; // nums is unaffected still } // The floppy disk here is the (number) 0 passed after the function: }, 0); // 30 (6 + 7 + 8 + 9)
Это пройдет через наш массив nums
, проверьте, равно ли значение moreThanFive
. Если это так, он сохранит его на дискету (спиральный блокнот в линейку).
Вы можете думать об этом как о функции map
, но все результаты сводятся к одному.
Это, вероятно, более сложный из трех. Уделите некоторое время тому, чтобы убедиться, что вы поняли что, а не как.
Итак, давайте попробуем написать нашу собственную функцию сокращения:
const reduce = (floppyDisk, arr) => { for(let i = 0; i < arr.length; i++) { if (arr[i] > 5) { floppyDisk += arr[i]; } }; return floppyDisk; // nums is still not a mutant/zombie/whatever }
Это очень простая версия — она будет работать только с числами, но вы поняли идею. Версия JS намного мощнее — она может нажимать на массивы и т. д.
НО, вы можете видеть, что это все ещепросто for
loop с проверкой if
внутри него.
Я надеюсь, что это демистифицирует эти три функции более высокого порядка. Я думаю, что я был достаточно уверен в их использовании раньше, но написание этого действительно прояснило для меня.
Попробуйте их, посмотрите, как это работает для вас.
Если это поможет, дайте мне знать — оставьте комментарий, ретвит или что-то еще.
Если это не так, дайте мне знать.
Возможно, есть лучший способ сделать это, чем этот, но я старался сделать его ясным и простым.
ПОМНИТЕ ДЕТЕЙ: сокращение переменных до одной буквы ДЕЙСТВИТЕЛЬНО никому не помогает, когда они пытаются учиться.