Что такое функциональное программирование?
С ростом сложности программ и их бесчисленных состояний мы возвращаемся к старой парадигме: функциональной. Многие программисты уже привыкли к объектно-ориентированной парадигме, поэтому я начну приводить сравнение. Объектно-ориентированное программирование имеет множество шаблонов и правил, но мы можем обобщить их следующим образом: Объектно-ориентированное программирование налагает дисциплину на косвенную передачу управления данными, и это по сравнению с функциональным программированием Функциональное программирование налагает дисциплину на присвоение. Эти две фразы многое говорят о соответствующих парадигмах, которые мы рассмотрим в этой статье блога.
Что это для меня?
Когда вы познаете новую парадигму, вы получаете оружие для решения старых проблем еще проще, чем раньше. Я покажу «чистый» скрипт на TypeScript, затем другой нечистый скрипт на TypeScript и, наконец, хороший пример того же упражнения на clojure.
Ниже я приведу весь код скрипта и давайте проанализируем и поймем, что происходило
Начало
const customFunction = (
value1: number,
value2: number,
funcEvaluater: string,
textObj: { todayText: string; tomorrowText: string }
) => {
function createdFunction(funcEvaluater) {
return eval(funcEvaluater);
}
const currier =
(textObj: { todayText: string; tomorrowText: string }) =>
(val2: string) =>
(val3: string) => {
return textObj.todayText + val2 + textObj.tomorrowText + val3;
};
return [value1 + value2, createdFunction(funcEvaluater), currier(textObj)];
};
const [sum, evaluator, curryWhater] = customFunction(2, 3, '3 + 3', {
todayText: 'Today is: ',
tomorrowText: ' and tomorrow will be: ',
});
const getTodayWheterApi = (userLocation: string) => {
// call api
return 'Raining';
};
const getTomorrowWheterApi = (userLocation: string) => {
// call api
return 'Sunny';
};
// Chama a localização do user aqui
const currentLocation = 'userCurrentLocation';
appDiv.innerHTML = `
<h2>value: ${sum}</h2>
<h3>Eval: ${evaluator} </h3>
<h3>curry: ${curryWhater(getTodayWheterApi(currentLocation))(getTomorrowWheterApi(currentLocation))} </h3>`;
Чистые функции
Единственный идеальный способ обертывания — это функции. Мы можем видеть в нашей функции под названием customFunction
const customFunction = (
value1: number,
value2: number,
funcEvaluater: string,
textObj: { todayText: string; tomorrowText: string }
) => {
function createdFunction(funcEvaluater) {
return eval(funcEvaluater);
}
const currier =
(textObj: { todayText: string; tomorrowText: string }) =>
(val2: string) =>
(val3: string) => {
return textObj.todayText + val2 + textObj.tomorrowText + val3;
};
return [value1 + value2, createdFunction(funcEvaluater), currier(textObj)];
};
Все, что вы используете в этой функции, вы получаете как параметр, и все равно есть возврат, так что вы заканчиваете работу, делаете какой-то расчет, вызов api или любую другую обработку в потоке данных и, наконец, возвращает то, что вы сделали, в этом случае мы получили 4 аргумента, 2 значения, которые будут добавлены и возвращены первыми, функция, которая будет оценена (не используйте eval в production) и, наконец, возвращается также помимо результата eval, карри на основе объекта, который был передан в качестве параметра, карри в нескольких словах — это функция, которая генерирует другую функцию.
Каков результат этого?
Поскольку это чистая функция и легко усваивается, благодаря идеальной инкапсуляции, из нее мы получаем результаты ее использования.
const [sum, evaluator, curryWhater] = customFunction(2, 3, '3 + 3', {
todayText: 'Today is: ',
tomorrowText: ' and tomorrow will be: ',
});
И результат в пользовательском интерфейсе будет выглядеть следующим образом
const getTodayWheterApi = (userLocation: string) => {
// call api
return 'Raining';
};
const getTomorrowWheterApi = (userLocation: string) => {
// call api
return 'Sunny';
};
// Chama a localização do user aqui
const currentLocation = 'userCurrentLocation';
html do user
appDiv.innerHTML = `
<h2>value: ${sum}</h2>
<h3>Eval: ${evaluator} </h3>
<h3>curry: ${curryWhater(getTodayWheterApi(currentLocation))(getTomorrowWheterApi(currentLocation))} </h3>`;
на приведенном ниже рисунке показан результат
Это делает любую часть кода легко настраиваемой, поскольку на каждом шаге мы имеем константу, полученную в результате функции.
И как это будет выглядеть на практике?
Мы можем рассмотреть классический случай цикла for, я сделал один и тот же цикл for дважды в typescript, чтобы показать более современную версию старого и, на мой взгляд, даже более красивую
// for loop classico
for (let i = 0; i < 30; i++) {
console.log(i * i);
}
// for loop moderno -> seu array = Array.from(Array(30).keys())
for (const [index, value] of Array.from(Array(30).keys()).entries()) {
console.log(value * value);
}
// exemplo prático do array moderno
const array = [1, 2, 3, 4, 5, 6, 7, 8];
for (const [index, value] of array.entries()) {
console.log(value);
}
Базовый, не так ли? Он относительно прост и понятен, но в то же время меня раздражает классика, тот факт, что у нас есть переменная, которая впоследствии будет отброшена.
В Clojure у нас есть такое же решение этой проблемы, которое я считаю чрезвычайно красивым:
;; resolver função que faz o quadrado dos primeiros 30 numeros inteiros
(defn create-n-list
"creates a n list"
[numbers]
(take (+ numbers 1) (range)))
(defn first-n-multiples "recebe n numeros e retorna uma lista com seus multiplos"
[numbers]
(map * (create-n-list numbers) (create-n-list numbers)))
;; (first-n-multiples 30)
;;=> (0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 441 484 529 576 625 676 729 784 841 900)
хотя я создал две функции, есть способы сделать это через анонимную функцию, но в этом случае очень четко видно, что именно ищется. Этот возвращенный список может быть использован в качестве константы и так далее.
Если у вас возникнут трудности с чтением этого кода на Clojure, напишите мне DM в twitter, я буду очень рад помочь вам! Наибольшую трудность для многих людей при работе с clojure представляет его инфиксная нотация, как в примере с умножением (* n n).
Заключение
Поскольку знаний никогда не бывает слишком много, я считаю, что дать шанс и попробовать писать более функциональный код, с меньшим количеством состояний и большим количеством функций и констант, может быть хорошей практикой для будущего в целом. Я рекомендую познакомиться с некоторыми вариантами LISP и влюбиться в то, как такой простой язык может быть настолько мощным.