Функционално програмиране означава използване на функции с най-добър ефект за създаване на чист и поддържан софтуер. Тази статия илюстрира концепциите зад функционалната парадигма с практически примери в JavaScript и Java.
Функционалното програмиране е актуално в разработката на софтуер от много отдавна, но придобива ново значение в съвременната ера.
Какво е определението за функционално програмиране?
Функциите са основни за организацията на кода. Те съществуват във всички езици за програмиране от по-висок ред. Като цяло функционалното програмиране означава използване на функции с най-добър ефект за създаване на чист и поддържан софтуер. По-конкретно, функционалното програмиране е набор от подходи за кодиране, обикновено описвани като парадигма за програмиране.
Функционалното програмиране понякога се дефинира в противоположност на обектно-ориентираното програмиране (ООП) и процедурното програмиране. Това е подвеждащо, тъй като тези подходи не се изключват взаимно и повечето системи са склонни да използват и трите.
Функционалното програмиране предлага ясни предимства в определени случаи, използва се широко в много езици и рамки и е забележимо в съвременните софтуерни тенденции. Това е полезен и мощен инструмент, който трябва да бъде част от концептуалния и синтактичен инструментариум на всеки софтуерен разработчик.
Чисти функции
Идеалът във функционалното програмиране е това, което е известно като чисти функции. Чиста функция е тази, чиито резултати зависят само от входните параметри и чиято операция не инициира никакъв страничен ефект, тоест не прави външно въздействие освен връщаната стойност.
Красотата на чистата функция е в нейната архитектурна простота. Тъй като чистата функция се свежда само до аргументите и връщаната стойност (т.е. нейния API), тя може да се разглежда като задънена улица на сложността. Единственото й взаимодействие с външната система, в която работи, е чрез дефинирания API.
Това е в контраст с OOП, където обектните методи са проектирани да взаимодействат със състоянието на обекта (членовете на обекта), и за разлика от кода на в процедурен стил, където външното състояние често се манипулира от функцията.
В реалната практика обаче, често се налага функцийте да взаимодействат с по-широкия контекст, както се вижда от куката „useEffect“ на React.
Неизменяемост
Друг принцип на философията на функционалното програмиране е, да не е се променят данните извън функцията. На практика това означава да се избягва промяната на входните аргументи във функция. Вместо това връщаната стойност на функцията трябва да отразява извършената работа. Това е начин за избягване на странични ефекти. Това улеснява разсъжденията за ефектите на функцията, тъй като тя работи в рамките на по-голямата система.
Функции от първи клас
Отвъд идеала за чистата функция, в действителната практика на кодиране, функционалното програмиране зависи от първокласни функции. Първокласна функция е функция, която се третира като „нещо само по себе си“, способно да стои самостоятелно и да се третира независимо. Функционалното програмиране се стреми да се възползва от езиковата поддръжка при използване на функции като променливи, аргументи и връщащи стойности за създаване на елегантен код.
Тъй като първокласните функции са толкова гъвкави и полезни, дори и силните ООП езици като Java и C# са се принудили да включат поддръжка на първокласни функции. Това е стимулът зад поддръжката на Java 8 за ламбда изрази.
Друг начин за описване на първокласни функции са функциите като данни. Тоест, функция от първи клас може да бъде присвоена на променлива както всички други данни. Когато пишете: „let myFunc = function () {}“, използвате функция като данни.
Функции от по-висок ред
Функция, която приема функция като аргумент или връща функция е, известна като функция от по-висок ред-функция, която работи върху функция.
И JavaScipt, и Java са добавили подобрен синтаксис на функциите през последните години. Java добави оператора стрелка и оператора с двойно двоеточие. JavaScript добави само оператора стрелка. Тези оператори са предназначени да улеснят дефинирането и използването на функции, особено вградени като анонимни функции. Анонимна функция е тази, която е дефинирана и използвана, без да й е дадена референтна променлива.
Пример за функционално програмиране
Може би най-известният пример за това, където функционалното програмиране блести е, в работата с колекции. Това е така, защото възможността да се прилагат парчета функционалност към елементите в колекция е естествено прилягане към идеята за чиста функция.
Помислете за Списък 1, който се възползва от функцията JavaScript map () за главни букви в масив.
Списък 1. Използване на map () и анонимна функция в JavaScript:
- нека букви = [„a“, „b“, „c“];
- console.info (letters.map ((x) => x.toUpperCase ()));
- // изходи [„A“, „B“, „C“]
Красотата на този синтаксис е, че кодът е силно фокусиран. Не се изискват императивни канални инсталации, като например манипулиране на цикъл и масив. Мисловният процес на това, което се прави е, ясно изразен от този код.
Същото се постига с оператора със стрелки на Java, както се вижда в Списък 2.
Списък 2. Използване на map () и анонимна функция в Java:
- импортиране на java.util.*;
- импортиране на java.util.stream.Collectors;
- импортиране на статичен java.util.stream.Collectors.toList;
- // …
- Низходящ списък = Arrays.asList („a“, „b“, „c“);
- System.out.println (lower.stream (). Map (s -> s.toUpperCase ()). Collect (toList ()));
- // изходи [„A“, „B“, „C“]
„Списък 2“ използва библиотеката за потоци на Java 8, за да изпълнява същата задача за горния регистър на списък с букви. Забележете, че синтаксисът на оператора на основната стрелка е практически идентичен с JavaScript и те правят същото, т.е.създават функция, която приема аргументи, изпълнява логика и връща стойност. Важно е да се отбележи, че ако така дефинираното тяло няма скоби около него, тогава връщаната стойност се дава автоматично.
Продължавайки с Java, помислете за оператора с двойно двоеточие в Списък 3. Този оператор ви позволява да се позовавате на метод в клас. В този случай на метода toUpperCase в класа String. Спискъ 3 прави същото като Списък 2. Различните синтаксиси са полезни за различни сценарии.
Списък 3. Java двоен оператор на двоеточие:
- // …
- Възходящ списък= lower.stream (). Map (String :: toUpperCase) .collect (toList ());
И в трите примера по-горе можете да видите, че функциите от по-висок ред работят. Функцията map () и в двата езика приема функция като аргумент.
Казано по друг начин, можете да гледате на преминаването на функции в други функции (в API на масив или по друг начин) като функционални интерфейси. Функциите на доставчика, които консумират функциите на параметрите, са приставки за обобщена логика.
Това много прилича на модел на стратегия в ООП, но компактността на функцията създава много стегнат протокол за компоненти.
Като друг пример, помислете за Списък 4, който определя манипулатор на маршрут в рамката Express за Node.js.
Списък 4. Функционален манипулатор на маршрути в Express:
- var express = require (‘express’);
- var app = express ();
- app.get (‘/’, function (req, res) {
- res.send (‘One Love!’);
- });
Списък 4 е добър пример за функционално програмиране, тъй като позволява чистото дефиниране на точно какво е необходимо за картографиране на маршрут и обработка на заявки и отговори, въпреки че може да се твърди, че манипулирането на обекта на отговор в тялото на функцията е страничен ефект.
Къри функции
Сега помислете за понятието функционално програмиране на функции, които връщат функции. Това се среща по-рядко от функциите като аргументи. Списък 5 има пример от общ модел на React, където синтаксисът на одебелената стрелка е верижен.
Списък 5. Кърирана функция в React:
- handleChange = поле => e => {
- e.preventDefault ();
- // Обработка на събитие}
Целта на горното е да създаде манипулатор на събития, който да приеме въпросното поле, а след това и събитието. Това е полезно, защото можете да приложите един и същ handleChange към множество полета. Накратко, един и същ манипулатор може да се използва в множество полета.
Списък 5 е пример за функция с къри. „Кърирана функция“ е малко разочароващо име. Тези функции почитат човек, което е хубаво, но не описват понятието, което всъщност е объркващо. Във всеки случай идеята е, че когато имате функции, които връщат функции, можете да свързвате заедно обаждания към тях по по-гъвкав начин, отколкото създаването на една функция с множество аргументи.
Когато извиквате този вид функции, ще срещнете отличителния синтаксис на „скобени скоби“: handleChange (поле) (събитие).
Програмиране в големи размери
Предходните примери предлагат практическо разбиране на функционалното програмиране във фокусиран контекст, но функционалното програмиране има за цел да доведе до по-големи ползи от програмирането като цяло. Казано по друг начин, функционалното програмиране има за цел да създаде по-чисти, по-устойчиви и мащабни системи.
Трудно е да се дадат примери за това, но един реален пример е ходът на React за популяризиране на функционални компоненти. Екипът на React отбелязава, че по-краткият функционален стил на компонента предлага предимства, които се комбинират с увеличаването на интерфейсната архитектура.
Друга система, която използва широко функционално програмиране е, ReactiveX. Мащабните системи, изградени върху вида потоци от събития, които ReactiveX използва, могат да се възползват от взаимодействието на отделен софтуерен компонент. Angular напълно приема ReactiveX (RxJS) навсякъде като потвърждение на тази мощ.
Променлив обхват и контекст
И накрая, проблем, който не е непременно част от функционалното програмиране като парадигма, но е много важно да се обърне внимание, когато се прави функционално програмиране е, този с променлив обхват и контекст.
В JavaScript контекстът конкретно означава това, което разрешава тази ключова дума. В случая на оператора със стрелка на JavaScript, това се отнася до ограждащия контекст. Функция, дефинирана с традиционния синтаксис, получава свой собствен контекст. Манипулаторите на събития в DOM обекти могат да се възползват от този факт, за да гарантират, че ключовата дума „this“ се отнася до елемента, който се обработва.
Обхватът се отнася до променливия хоризонт, тоест какви променливи са видими. В случай на всички функции на JavaScript (както с дебела стрелка, така и с традиционни) и в случай на анонимни функции, дефинирани със стрелки на Java, обхватът е, този на ограждащото тяло на функциите, макар, че в Java само тези променливи, които са ефективно окончателни, могат да бъдат достъпени. Ето защо такива функции се наричат затваряне. Терминът означава, че функцията е затворена в нейния обхват.
Това е важно да се помни – такива анонимни функции имат пълен достъп до променливите в обхвата. Вътрешната функция може да работи срещу променливите на външната функция. Това може да се счита за бъгав страничен ефект на функцията.
Разберете какво е процедурно програмиране