Васильев Андрей Михайлович, 2022
Версии презентации
Действия | Вычисления | Данные |
---|---|---|
Зависят от количества вызовов и времени | Вычисляют значение на основе входных данных | Факты о событиях |
Также называются: функции с побочными эффектами, нечистые функции | Также называются: чистые функции, математические функции | |
Примеры: отправка почты, чтение из базы данных | Примеры: Нахождение максимального числа, проверка корректности почтового адреса | Примеры: почтовый адрес, который ввёл пользователь, стоимость доллара, прочитанного из API банка |
Запрос к вычислению может быть заменён данными
Например сложение, +
, является вычислением. Вычисление 3 + 2
можно заменить числом 5
Рассмотрим процесс совершения покупок в виде диаграммы временной последовательности
Все шаги являются действиями, но где же остальные типы кода?
Зависит от времени проверки холодильника, однако список продуктов в холодильнике — это данные, назовём их текущим складским запасом
Поездка в магазин является действием, которое опирается на множество данных: местоположение магазина, маршрут и т.д.
Самый простой алгоритм для выполнения покупок: составить список покупок и набрать продуктов согласно ему
необходимый запас - текущий складской запас = список покупок
Как и поездка в магазин является действием, которое мы также не будем разделять на части
Данную модель можно далее детальнее разбирать на типы, делая её более глубокой
Данные содержат факты о произошедших событиях
В языке JavaScript можно использовать встроенные типы данных: числа, строки, массивы и объекты
В других языках, например в Haskell, можно определить свои типы, описывающие предметную область
Данные сохраняют знания в своей структуре. Структура данных должна соответствовать предметной области, в которой оперирует приложение
Для реализации неизменяемости данных применяются подходы:
Данные наиболее ценны тем, что мы не можем с ними сделать: их невозможно выполнить, изменить. В результате они понятны
Вычисления и действия не обладают этими достоинствами
Открытость для интерпретации является также и недостатком: их необходимо интерпретировать, чтобы данные стали полезными
Вычисления могут быть выполнены и могут быть полезны, даже если их трудно понять
Данным требуется вычисления для их интерпретации
Одним из ключевых навыков программиста на функциональных языках является способность представления данных, чтобы их можно было интерпретировать сейчас и заново интерпретировать в будущем
Все ли данные содержат описания фактов о произошедших событиях? Что же тогда делать с фактами о человеке или другой сущности?
Данные приходят в информационную систему в определённое время
Имя человека может попасть систему через оправку формы регистрации на веб-странице приложения. Данный запрос был обработан, интерпретирован и нужные данные были сохранены в базу данных
Их можно интерпретировать как имя человека, но они появились в результате события — отправки запроса
Полезные свойства определения данных как фактов о событиях:
Рассмотрим систему по раздаче скидочных купонов. Люди могут подписаться на интернет-рассылку и раз в неделю получать на почту сообщение с актуальными купонами
Компания решила реализовать новую систему привлечения новых клиентов: если человек порекомендует систему 10 своим знакомым, то они будут получать лучшие купоны
Таблица пользователей
rec_count | |
---|---|
lena@mail.ru | 2 |
pasha@yandex.ru | 16 |
ivan@example.com | 1 |
dima@bz.com | 0 |
tanja@lol.com | 25 |
bot@botnet.com | 0 |
Таблица купонов
coupon | rank |
---|---|
MAYDISCOUNT | good |
10PERCENT | bad |
PROMOTION45 | best |
IHEARTYOU | bad |
GETADEAL | best |
ILIKEDISCOUNTS | good |
Команда разработчиков составила список шагов, которые необходимо выполнить, чтобы реализовать новую стратегию
Определим категорию для каждого из элемента решения
Рассмотрим один из подходов к решению поставленной задачи отправки разных почтовых сообщений в зависимости от количества рекомендаций
Получение списка является действием, т.к. зависит от времени, когда мы выполняем запрос к базе данных
Полученный список подписчиков является данными
Получение купонов тоже является действием. Купоны в базе данных постоянно изменяются.
Полученный список купонов является данными
В рамках данного шага будут созданы новые данные. Этот подход часто используется при использовании ФЯП: данные создаются отдельно от места их дальнейшего использования
Такой же подход использовался ранее, когда мы вычисляли список покупок до поездки в магазин
После формирования списка сообщений для отправки осталось только лишь выполнить данный план
Рассмотрим в деталях процесс формирования текстов писем для отправки
После формирования плана перейдём к реализации отправки почтовых сообщений
Начнём с представления подписчика, будем использовать объекты JavaScript
var subscriber = {
email: "pavel@mail.ru",
rec_count: 16
}
Для представления качества купона будем использовать строки
var rank1 = "best";
var rank2 = "good";
Задача определения качества купона — это вычисление, которое можно удобно реализовать с помощью функции
function subscriberCouponRank(subscriber) {
if(subscriber.rec_count >= 10) {
return "best";
} else {
return "good";
}
}
Данную функция не содержит побочных эффектов
Для представления купона будем использовать объекты JavaScript:
var coupon = {
coupon: "10PERCENT",
rank: "bad"
}
Реализуем функцию для формирования списка купонов по рангу
function selectCouponsByRank(coupons, rank) {
var result = [];
for(var i = 0; i < coupons.length; i++) {
var coupon = coupons[i];
if(coupon.rank === rank) {
result.push(coupon);
}
}
return result;
}
Для представления почтового сообщения тоже используем объекты:
var message = {
from: "newsletter@coupondog.co",
to: "user@mail.ru",
subject: "Ваши купоны на эту неделю",
body: "Здравствуйте. Ваши купоны на эту неделю..."
}
function emailForSubscriber(subsriber, goods, bests) {
var rank = subscriberCouponRank(subscriber);
if(rank === "best") {
return {
from: "newsletter@coupondog.co",
to: subscriber.email,
subject: "Ваши лучшие купоны на эту неделю",
body: "Ваши лучшие купоны: " + bests.join(", ")
};
} else {
return {
from: "newsletter@coupondog.co",
to: subscrber.email,
subject: "Ваши хорошие купоны на эту неделю",
body: "Ваши хорошие купоны: " + goods.join(", ")
};
}
}
Используя предыдущие функции (вычисления) сформируем список сообщений для всех подписчиков
function emailsForSubscribers(subscribers, goods, bests) {
var emails = [];
for(var i = 0; i < subscribers.length; i++) {
var subscriber = subscribers[i];
var email = emailForSubscriber(subscriber, goods,
bests);
emails.push(email);
}
return emails;
}
Данная функция является вычислением, т.к. зависит только от аргументов и не формирует побочные эффекты
Рассмотрим вариант реализации отправки почтовых сообщений
function sendCoupons() {
var coupons = fetchCouponsFromDB();
var goodCoupons = selectCouponsByRank(coupons, "good");
var bestCoupons = selectCouponsByRank(coupons, "best");
var subscribers = fetchSubscribersFromDB();
var emails = emailsForSubscribers(subscribers,
goodCoupons, bestCoupons);
for(var i = 0; i < emails.length; i++) {
var email = emails[i];
emailSystem.send(email);
}
}
Такой подход к решению задачи часто используется программистами на функциональных языках
В текущей реализации мы формируем почтовые сообщения перед отправкой. Что делать в случае, когда подписчиков будет несколько миллионов?
Правильный ответ: изначально это неизвестно, всё будет сильно зависеть от среды исполнения нашего кода и её возможностей
В качестве решения данной проблемы в рамках представленной реализации можно разбить всех пользователей на части
function sendCoupons() {
var coupons = fetchCouponsFromDB();
var goodCoupons = selectCouponsByRank(coupons, "good");
var bestCoupons = selectCouponsByRank(coupons, "best");
var page = 0;
var subscribers = fetchSubscribersFromDB(page);
while(subscribers.length > 0) {
var emails = emailsForSubscribers(subscribers,
goodCoupons, bestCoupons);
for(var i = 0; i < emails.length; i++) {
var email = emails[i];
emailSystem.send(email);
}
page++;
subscribers = fetchSubscribersFromDB(page);
}
}
Вычисления — это операции по формированию результатов в зависимости от входящих значений. Не зависят от времени запуска или количества запусков
Важная особенность ФП — вынесение логики в вычисления из действий
Вычисления гораздо легче воспринимать, так как можно не задаваться следующими вопросами:
Невозможно узнать результат вычисления без запуска (как и у действия)
Конечно можно прочитать исходный код, но это не гарантирует 100% понимания. С точки зрения программы м только можем запустить функцию
Рассмотрим существующий код по отправке комиссионных. sendPayout()
является действием, которое переводит деньги на банковский счёт
function figurePayout(affiliate) {
var owed = affiliate.sales * affiliate.commission;
if(owed > 100) {
sendPayout(affiliate.bank_code, owed);
}
}
function affiliatePayout(affiliates) { // Начальная функция
for(var i = 0; i < affiliates.length; i++) {
fiurePayout(affiliates[i]);
}
}
function figurePayout(affiliate) {
var owed = affiliate.sales * affiliate.commission;
if(owed > 100) {
sendPayout(affiliate.bank_code, owed); // Действие
}
}
function affiliatePayout(affiliates) {
for(var i = 0; i < affiliates.length; i++) {
fiurePayout(affiliates[i]);
}
}
function main(affiliates) {
affiliatePayout(affiliates);
}
function figurePayout(affiliate) { // Действие
var owed = affiliate.sales * affiliate.commission;
if(owed > 100) {
sendPayout(affiliate.bank_code, owed); // Действие
}
}
function affiliatePayout(affiliates) {
for(var i = 0; i < affiliates.length; i++) {
fiurePayout(affiliates[i]); // Действие
}
}
function main(affiliates) {
affiliatePayout(affiliates);
}
function figurePayout(affiliate) { // Действие
var owed = affiliate.sales * affiliate.commission;
if(owed > 100) {
sendPayout(affiliate.bank_code, owed); // Действие
}
}
function affiliatePayout(affiliates) { // Действие
for(var i = 0; i < affiliates.length; i++) {
fiurePayout(affiliates[i]); // Действие
}
}
function main(affiliates) {
affiliatePayout(affiliates); // Действие
}
function figurePayout(affiliate) { // Действие
var owed = affiliate.sales * affiliate.commission;
if(owed > 100) {
sendPayout(affiliate.bank_code, owed); // Действие
}
}
function affiliatePayout(affiliates) { // Действие
for(var i = 0; i < affiliates.length; i++) {
fiurePayout(affiliates[i]); // Действие
}
}
function main(affiliates) { // Действие
affiliatePayout(affiliates); // Действие
}
Из примера видно, что действия делают код трудным: они распространяются в исходном коде
Если в функции применяется действие, то она тоже становится действием
Программисты на ФЯП используют действия (без этого приложения были бы бесполезны)
Применение действий происходит осознанно, к ним уделяется дополнительное внимание
Действия могут принимать различные формы
В большинстве языков программирования действия не отделены от вычислений
Рассмотрим примеры из языка JavaScript
alert("Привет!");
console.log("Был тут");
new Date();
y
user.first_name
stack[0]
z = 4
delete user.first_name
Любой код может стать действием, если начинает зависеть от количества обращений и времени обращения к ним
Действие — любой шаг, который может изменить состояние всей системы или зависит от него
В языке JavaScript действия реализуют в виде функций
Смысл действий заключается в изменении мира (состояния всей системы)