Предыдущие части: Часть 1
Добро пожаловать в параллельный мир
Вам необходимо кое-что узнать прежде, чем мы начнем… Если вы когда-либо программировали в JS, вы, вероятно, раньше использовали шаблоны FP! Данные шаблоны и подходы присутствовали всегда: мы просто не могли их правильно рассмотреть. Мы начнем с уже известного, а затем перейдем к исследованию новой территории. Кое-что может быть немного, скажем, странно. Но не бойтесь! Вместе мы справимся!
Полноправные функции
В Javascript функции являются объектами первого уровня. Как я уже упоминал ранее, нам не нравится загадочная терминология, поэтому давайте разъясним. Согласно глоссарию разработчиков Mozilla:
Считается, что язык программирования имеет полноправные функции, если функции в данном языке рассматриваются в качестве любой другой переменной. Например, в таком языке функция может передаваться как аргумент другим функциям, может быть возвращена другой функцией и может быть назначена в качестве значения переменной.
Функции как самозначимые группы символов
В следующем примере мы объявим самозначимую группу символов (const) и назначим ей анонимные функции стрелок.
После первоначального присваивания constFunction является самозначимой группой символов со значением функции. Мы проверяем это путем регистрации переменной constFunction в инструменте исследования Chrome. Поскольку constFunction ‒ это функция, которую мы также можем вызвать.
Функции как значения ключей объекта
Теперь, когда мы понимаем, что переменные могут содержать функции, давайте продемонстрируем функцию как значение ключа в объекте. Это должно быть знакомо тем, кто раньше занимался объектно-ориентированным программированием.
const functionAsObjectProperty = { print: (value) => console.log(value) }; functionAsObjectProperty.print("mic check"); // "mic check"
Функции как элементы массива
Когда функции являются объектами первого уровня, мы можем передавать их в качестве данных в массив, как и любой другой типданных. Давайте используем консоль Chrome и проверим это.
Функции высшего порядка
Теперь, когда мы разогрелись, давайте займемся интересным 🙂 Разработчики JS видят функции, которые ежедневно принимают другиефункции в качестве аргументов. Если вы переходите с языка, который не поддерживает FP, это должно показаться немного странным ??????? Давайте ознакомимся с данной концепцией, рассмотрев некоторые примеры.
Асинхронная функция, которая принимаетфункцию обратного вызова.
const jsonfile = require('jsonfile') const file = '/tmp/data.json' const obj = {name: 'JP'} const errorLoggerFunction = (err) => console.error(err); jsonfile.writeFile(file, obj, errorLoggerFunction)
В данном примере мы используем модуль jsonfile npm для метода writeFile. Третий параметр, который ожидается writeFile, является функцией. Когда метод jsonfile.writeFile выполняет его, он либо будет успешным, либо завершится ошибкой. В противном случае он выполнит функцию errorLoggerFunction. В качестве альтернативы мы могли бы использовать более краткую синтаксическую структуру и посмотреть указанную функцию:
const jsonfile = require('jsonfile') const file = '/tmp/data.json' const obj = {name: 'JP'} jsonfile.writeFile(file, obj, (err) => console.error(err))
setTimeout
const timeout = () => { setTimeout(() => alert("WoW"), 1000); }
В данном примере показан встроенный асинхронный метод setTimeout, который принимает 2 аргумента. Давайте немного упростим это и объясним функцию setTimeout в терминах функционального программирования.
Начнем с чтения подписи функции. Мы можем заметить, что количество аргументов, которые принимает setTimeout, равно двум. В функциональном программировании число аргументов, которыепринимает функция, называется «арность», произошедшее от таких слов, как унарный, двоичный, тернарный и т.д. Таким образом, мы можем сказать, что setTimeout имеет арность 2 или можно сказать, что она имеет двоичную (бинарную) функцию.
Аргументами, которые ожидаются setTimeout, является функция и временной интервал ожидания перед выполнением данной функции. Хм … Другая функция, которая принимает функцию в качестве входа?
В функциональном программировании это настолько распространено, что данные типы функций даже имеют название! Они называются функциямиболее высокого порядка.
Функция высшего порядка – это функция, которая принимает функцию в качестве аргумента или возвращает функцию.
Итак. Теперь вы можете незаметно упомянуть это в любом случайном разговоре на работе / с друзьями и выглядеть, как босс! ? ???
Давайте немного повеселимся и создадим массив (список) функций в следующем примере.
const add = (x,y) => x + y; const subtract = (x,y) => x - y; const multiply = (x,y) => x * y; const arrayOfFunctions = [add, subtract, multiply]; arrayOfFunctions.forEach(calculationFunction => console.log(calculationFunction(1,1))); // 2 0 1
В строке 5 мы объявляем массив функций. Затем мы используем метод forEach для итерации по массиву. forEach ‒ это поддерживаемая пользователем функция ES6+, которая принимает функцию, выполняемую для каждого элемента массива. Следовательно, forEach также являетсяфункцией высшего порядка!
Наш forEach принимает анонимную функцию в качестве входных данных. forEach будет перебирать массив, неявно обращаться к текущему элементу массива и называть его getCalculation. Стоит отметить, что forEach неявно обращается к элементам массива по сравнению с тем, как мы могли бы получить доступ к текущему элементу, если бы мы использовали регулярный цикл с параметром, то есть, arrayOfFunctions [i]. Каждый элемент в нашем массиве является функцией, поэтому мы вызываем getCalculation с аргументами, которые она ожидает.
Фантастика. Данный пример иллюстрирует, что функции вфункциональном программировании могут быть переданы в массивы (списки), как и любой другой тип данных. Функции могут быть переданы куда угодно!
Теперь давайте построим собственную функцию более высокого порядка!
const addWrapper = () => (x,y) => x + y; const add = addWrapper(); const sum1 = add (1,2); // 3 // Or we could do it like this const sum2 = addWrapper()(4,4); // 8
Функция addWrapper возвращает простую функцию добавления при вызове. Вызывая результат функции addWrapper и предоставляя ему два аргумента, мы имеем доступ к функции анонимного добавления.
Мы могли бы пойти еще дальше и написать такую функцию, которая возвращает функцию, которая, в свою очередь, возвращает свою собственную функцию!
const bankStatement = name => location => balance => `Hello ${name}! Welcome to the bank of ${location}. Your current balance is ${balance}`; const statementExpectingLocation = bankStatement("Omer"); const statementExpectingBalance = statementExpectingLocation("NYC"); const bankStatementMsg = statementExpectingBalance("100 million"); // wishful thinking? console.log(bankStatementMsg); // Hello Omer! Welcome to the bank of NYC. Your current balance is 100 million // We could also call the function with all the arguments up front const msg = bankStatement("Jeff Bezos")("Silicon Valley")("97.7 billion"); console.log(msg); // Hello Jeff Bezos! Welcome to the bank of Silicon Valley. Your current balance is 97.7 billion
Это очень мощный шаблон в функциональном программировании. Мы будем подробно изучать его в ближайших постах, когда будем говорить о каррировании и частичных приложениях.
Полноправные функции являются краеугольным камнем любого функционального языка программирования. Главное, что вы должны вынести из нашего обсуждения полноправных функций, это то, что функции могут быть назначены как самозначимые группы символов, переменные, помещены как элементы массива и даже заданы как значения ключей для объекта. Кроме того, (и, самое главное?!) функции могут быть возвращены в функции и из функций, как и любой другой тип данных!
Перевод статьи Omer Goldberg : Javascript and Functional Programming — Pt.2 : First Class Functions