Меню

Чистые функции в JavaScript

24.06.2018 - java script, ЯП

Functional Programming

Что такое функциональное программирование?

Это декларативный стиль программирования, который основан на трех главных правилах:
* Чистые функции
* Избегать сторонних эффектов
* Избегать стэйтов

Чистые функции (Pure functions)

Чистые функции — это когда при одних и тех же входных данных — мы получим одни и те же выходные данные. У чистых функций нет побочных эффектов.

Что такое побочные эффекты?
Это когда функция изменяет состояние приложения путем, отличающимся, от возврата значения. Чистая функция получает некоторые входные данные и модифицирует их в выходные данные. Если функция меняет состояние приложения, например изменяет объект переданный по ссылке, вывод чего либо или изменение глобального поля, то она перестает быть чистой.

Чистая функция

function sum(a, b) {
    return a + b;
}

Не чистая функция

class Calculator {
    constructor() {
        this.previousResult = null;
    }

    add(a,b) {
        this.previousResult = a + b; //side effect
        return this.previousResult;
    }
}

Общее состояние

Пример

Как мы видим, методы в этом классе не чистые

class ProfileManager {
    constructor(user) {
        this.user = user;
        this.profileUrl = "";
        this.username = "";
        this.domain = "http://mysocialnetwork,com";
    }

    createUsername() {
        let random = Math.floor(Math.random() * 100000);
        this.username = `${this.user.firstName}.${this.user.lastname}.${random}`;
    }

    createProfileUrl() {
        this.profileUrl = `${this.domain}${this.username}`;
    }
}

let user = {
    id: 7685, firstName: "John", lastName: "Smith"
};

let managet = new ProfileManager(user);
manager.createUsername();
manager.createProfileUrl();
console.log(manager);

Во первых функция createUsername — если мы введем одно и тоже значение дважды — мы получим разный результат. Во вторых мы видим shared state — так как она пишет результат в переменную this.username, которая также используется в функции createProfileUrl. Также createProfileUrl для совей корректной работы, ожидает что функция createUsername была уже выполнена.

Чтобы сделать методы чистыми, для начала давайте удалим свойства this.profileUrl и this.username из конструктора. Также нам нужно убрать из функции createUsername запись в общую переменную и убрать всю рандомность. Вместо рандомного числа, мы будем использовать id пользователя. Теперь давайте перейдем к методу createProfileUel. Вместо использования общих свойств, мы передадим в функцию два аргумента — domain и username. Теперь мы можем убрать this.domain из конструктора. Также в самой функции, вместо записи в переменную — мы вернем результат. Одна из главных особенностей чистых функций — это возможность объединять их.

class ProfileManager {
    constructor(user) {
        this.user = user;
    }

    createUsername(user) {
        return `${user.firstName}.${user.lastname}.${user.id}`;
    }

    createProfileUrl() {
        return `${domain}${username}`;
    }
}

let user = {
    id: 7685, firstName: "John", lastName: "Smith"
};

let manager = new ProfileManager(user);
let profileUrl = manager.createProfileUrl("http://mysocialnetwork.com", createUsername(user));

Как избежать побочных эффектов с помощью Object.assign и Spread оператора

Object.assign(target, ...sources) копирует все enumerable собственные свойства источников (sources) в целевой объект (target) и возвращает целевой объект.

Вы должны понимать, что Object.assign не делает глубокого клонирования.

Второй инструмент, который нам поможет это spread оператор. Он чем то похож на Object.assign, тем что тоже копирует enumerable свойства.

Посмотрим на примере:

let readings = {
    coreTemp: 74,
    additionalTemp: 80,
    readingA: 178,
    readingB: 120,
    readingC: -190
};

function adjustReadings(reading) {
    readings.readingA -= 20;
    readings.readingB += readings.coreTemp / 2;
}

function testReadingA(readingA) {
    return readingA >= 170;
}

console.log("Readings before : ", readings);
adjustReadings(readings);
console.log(testReadingA(readings.readingA));
console.log("Readings after : ", readings);

Мы видим, что у функции есть побочные эффекты. Она манибулирует shared states.

С помощью spread оператора легко сделать копию объекта, чтобы передать в функцию не по ссылке. Также теперь нам нужно вернуть значение из функции.

let readings = {
    coreTemp: 74,
    additionalTemp: 80,
    readingA: 178,
    readingB: 120,
    readingC: -190
};

function adjustReadings(reading) {
    readings.readingA -= 20;
    readings.readingB += readings.coreTemp / 2;
    return readings;
}

function testReadingA(readingA) {
    return readingA >= 170;
}

console.log("Readings before : ", readings);
let newReadings = adjustReadings({...readings}); // first variant
let newReadingsAnother = adjustReadings(Object.assign{{}, readings}); // second variant
console.log(testReadingA(readings.readingA));
console.log("Readings after : ", readings);
console.log("New readings: ", newReadings);

Теперь у нас чистая функция adjustReadings.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *