Чисті функції – функціональне програмування

Мова, що підтримує чисті функції, дозволяє визначати функцію, якої надаються всі необхідні дані, після чого вона повертає результат зухвалому клієнту Як було сказано раніше, концепція чистої функції связа з програмуванням, вільним від побічних ефектів

Використання методів обєктно-орієнтованого програмування подразевает написання коду з побічними ефектами Це приймається як належне, т к, застосовуючи методи обєктно-орієнтованого програмування, разрабоік повинен приховувати реалізацію і не відкривати стан обєкта

Але велика проблема побічних ефектів полягає в тому, що вони викликають стохастичне поведінка коду, що означає, що при одній і тій же ситуації сествует можливість декількох різних результатів Загалом, стохастичне поведінка небажано, тому що воно викликає неточності

Розглянемо наступну операцію складання:

2 + 2   =   4

Поведінка цієї коди не стохастичне, т к два плюс два завжди дорівнює Четем Інша відповідь отримати неможливо У точності і полягає суть математи Ми вводимо дані, натискаємо кнопку і отримуємо передбачуваний результат Уявіть собі, який виник би хаос, якби завтра два плюс два стало двцать У наступному коді наводиться приклад такого хаосу, т к клієнт, викликає метод GetMeAValue (), не знає, що очікувати:

class ClassWithSideEffects { public ClassWithSideEffects() {

_islnitialized = false

}

bool _islnitialized string _value

public void Initialize(string value) {

_islnitialized = true

_value = value

}

public string GetMeAValue() { if (_islnitialized) {

return _value

}

throw new NullReferenceException(&quotNot initialized&quot)

}

}

Клієнт, що викликає метод GetMeAValue про, має проблему, яка полягає в тому, що він отримає два різних відповіді, або два різних майбутніх Першим будимо є дійсний відповідь, а другий майбутнім – виняток Яке бущее клієнт отримає, знаходиться поза його контролем, що є стохастичним поведінкою Даний код є прикладом програмування з побічними еектамі, т к для того щоб код працював, необхідно попередньо викликати

метод initialize о Але клієнт методу GetMeAValue () не знає, що потрібно визать метод initialize (), т к у нього немає вказівки робити це

Простими словами, код без побічних ефектів являє собою функцію, Корая отримує всі свої дані за допомогою набору параметрів і повертає міфікаціі зухвалому клієнту Сама функції не зберігає ніяких модіфацій Розглянемо, наприклад, наступний код:

(vail, val2) =&gt vail + val2

Даний код вільний від побічних ефектів і не є стохастичним, т к повертається стан залежить тільки від параметрів, переданих коду При оіх і тих же параметрах результат завжди буде одним і тим же Тепер рассмоім наступний приклад:

(val) =&gt { valDoSomethingO return valGetMeAValueO }

Даний код не є чистою функцією, т к він викликає метод DosomethingO, який, ймовірно, змінить стан зовнішньої функції, таким чином, потенцльно створюючи ситуацію, коли якась дія має кілька майбутніх Мод DosomethingO може не проявляти стохастичного поведінки в контексті лямбда-вирази, але він може робити це у спосіб

Використання незмінних типів

Для написання імперативного коду, вільного від побічних ефектів, можна застосовувати незмінні обєкти (immutable objects) Насправді, ми іользовалі один незмінний тип у всій цій книзі Можете сказати, який це тип Строковий тип Значення, присвоєне строковому типу, не можна змінити Незмінні типи вирішують багато проблем, включаючи проблеми паралелізму, несуперечності і побічних ефектів Але незмінні типи мають і недоаток, який полягає в тому, що для них потрібно більше ресурсів і, залежно від коду, вони можуть сповільнити продуктивність

ПРИМІТКА

По своєму досвіду я можу сказати, що продуктивність незмінного типу зазвичай на такому ж рівні, як і продуктивність змінюваних типів Винятком яяется ситуація, коли код працює з обєктом, як з дрібномодульних обєктом, тобто створюючи і руйнуючи сотні тисяч примірників одночасно

Розглянемо модифікований код класу ciasswithsideEffects, щоб зробити його незмінним:

class ClassWithNoSideEffects { private readonly string _value

public ClassWithNoSideEffects(string value) {

_value = value

}

public string GetMeAValue() { return _value

&gt&nbsp

}

Жирним шрифтом виділено єдине ключове слово readonly, з яким ми раніше не зустрічалися Коли це ключове слово застосовується до змінної класу, це означає, що даної змінної можна привласнювати значення при ініціалізації або в конструкторі, але після цього її значення змінювати не можна У наступному коді значення змінної з модифікатором readonly присвоюється при початковій ініціалізації:

class MyClass {

readonly int value = 10

&gt&nbsp

Клас classwithNosideEffects не має жодних побічних ефектів, т к метод GetMeAValue () завжди повертає один і той же результат: значення, присвоєне класу при його створенні конструктором У ініціалізації класу немає потреби, т к його примірник инициализируется при створенні Поєднання ініціалізації із створенням екземпляра класу означає, що завжди, коли нам потрібен обєкт, ми инициализируем його

Даний клас не проявляє стохастичного поведінки, тому його можна раматрівать логічно правильним Але не плутайте обставина, що клас лическими правильний і не має побічних ефектів з відсутністю помилок Значить, що повертається методом GetMeAValue (), все ще може бути неправильним, але, принаймні, можна з точністю сказати, що помилка лежить не в класі, а в коді, який створює екземпляр класу До написання коду з використанням незмінних обєктів необхідно звикнути, т к це інший спосіб програірованія

Тепер подивимося, як можна використовувати незмінюваний тип з прикладом для обчислення податку з продажів Цього разу ми також обчислюємо податок з продажів, але в контексті незмінного обєкта Приклад демонструє, як обєднати предмети імперативного програмування з методами функціонального программіранія, щоб отримати несуперечливий тип без побічних ефектів і проблем з паралелізмом Відповідний код виглядає таким чином:

class SalesTax {

private readonly double _percentage

public SalesTax(double percentage) {

_percentage = percentage

}

public double Percentage { get {

return percentage

&gt&nbsp

}

public double CalculateGrandTotal(double itemTotal) { return itemTotal + itemTotal * percentage

}

&gt&nbsp

Клас SalesTax має один член даних, percentage, який представляє Раєр податку у відсотках, що накладається на вартість придбаного товару Цей член даних оголошений readonly, таким чином, забезпечуючи, що йому можна ПРВО значення лише в конструкторі, після чого він залишається незмінним Раєр податку зчитується З ДОПОМОГОЮ властивості Percentage

Загальна сума, що включає податок з продажів, обчислюється методом CalculateGrandTotal (), який є чистою функцією, тк він завжди воращает один і той же результат Так як член даних percentag e модифікований ключовим словом readonly, ТО коли МИ доходимо ДО методу CalculateGrandTotal (), йому присвоюється значення, яке ніколи не змінюється

Маніпулювання незмінними типами

Але що нам робити з незмінними типами, коли нам потрібно змінити їх означати Наприклад, у багатьох країнах у готелях крім податку з продажів на рахунок налагтся ще й так званий туристичний податок, тобто вам потрібно платити податок за те, що ви турист Необхідність обєднання значень двох податків означає, що потрібно використовувати старий підхід роботи з незмінними типами і створити новий екземпляр типу Маніпулювати незмінним обєктом, застосовуючи підхід чистих функцій, можна за допомогою трьох різних методик: методами типу, зовнішніми виразами або методами розширення Кожна з цих трьох методик має свої переваги і недоліки

Маніпулювання незмінним обєктом за допомогою методу типу означає соан методу, який створює і повертає новий екземпляр типу, де метод обвлен в самому типі Цей підхід застосовується для маніпулювання строковим типом У разі класу SalesTax це означає перевизначення типу наступним чином:

class SalesTaxWithMethod {

private readonly double percentage

public SalesTaxWithMethod(double percentage) { percentage = percentage

}

public double Percentage { get {

return percentage

}

}

public double CalculateGrandTotal(double itemTotal) { return itemTotal + itemTotal * _percentage

}

public SalesTaxWithMethod AddPercentage(double percentage) { return new SalesTaxWithMethod(percentage + percentage)

}

}

Жирним шрифтом виділено код, який демонструє, яким чином значення туртіческого податку додається до значення податку з продажів, зазначеного як аргент методу AddPercentage () Нове значення передається як параметр конструора новому примірнику, який повертається клієнтові, зухвалому метод AddPercentage () Це пересічна операція і є чистою функцією, т к нічого не змінюється і повертається новий стан

ПРИМІТКА

Починаючи роботу з чистою функцією, легше створювати чисті функції, які залежать від цієї чистої функції Писати функціональний код легше, якщо почати з правильно підходу, а не намагатися застосовувати силове рішення

Візьміть до уваги такі аспекти щодо маніпуляції незмінюваність обєктами за допомогою методу типу:

• це легкий підхід, який не потребує вивчення нових програмних конструктів С #

• при цьому підході потрібна зміна базового типу, і якщо ви вже випустили промислову версію програми, то це означає необхідність зміни типу Але т к тип є незмінним, то стара логіка залишається нетрутой

• підхід злегка небезпечний, оскільки, як можна було бачити в чолі 8, предмети, асоційовані з типом, можуть змінювати приватні члени даних іншого примірника цього ж типу У прикладі з класом SalesTax це не є прлемой, т к застосовується ключове слово readonly Але це може стати прлемой, якщо ключове слово readonly не використовується

Зміна і маніпулювання незмінним типом допомогою зовнішнього важливий означає використання методу на іншому типі або лямбда-вираженні Два види податків, туристичний і з продажів, можна скласти за допомогою слідуючи лямбда-вирази:

(salesTax, percentage) =&gt new SalesTax(percentage + salesTaxPercentage)

Лямбда-вирази є найпростішим і найменш інвазивним підходом до маніпулювання незмінними обєктами При використанні даного підходу змінювати базовий тип немає потреби Але це також найбільш незручний

підхід, т к при ньому потрібна знати зовнішнє вираження Якщо ви його не знаєте, то, напевно, будете виконувати вже зроблену роботу

Використання методів розширення для маніпулювання незмінними обсягів по є моїм найкращим підходом, т к він поєднує прмущества методів типу та неінвазивності зовнішніх виразів, а також не має ніяких недоліків цих методів У цьому контексті, використовуючи методи розширені, можна маніпулювати незмінним обєктом таким чином, в результаті якого створюється новий незмінюваний обєкт

Працюючи з методами розширення, ми визначаємо зовнішнє вираження, але оголошено методу розширення робить метод таким, як нібито він був оголошений в типі Для класу SalesTax метод розширення був би оголошений наступним чином:

static class ClassExtension {

public static SalesTax AddPercentage(this SalesTax els,

double percentage) { return new SalesTax(elsPercentage + percentage)

}

}

Код реалізації виглядає подібно лямбда-виразу, але сигнатура методу побна методу типу

Загалом, при створенні чистих функцій з незмінними типами завжди найкраще використовувати методи розширення

Джерело: Гросс К С # 2008: Пер з англ – СПб: БХВ-Петербург, 2009 – 576 е: ил – (Самовчитель)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*