Hosszú idő után megtanultam és dolgoztam az objektum-orientált programozással, egy lépéssel hátrébb mentem, hogy átgondoljam a rendszer komplexitását.
"Complexity is anything that makes software hard to understand or to modify.
" - John Outerhout
Néhány kutatás után olyan funkcionális programozási fogalmakat találtam, mint a változtathatatlanság és a tiszta funkció. Ezek a fogalmak nagy előnyöket jelentenek a mellékhatásoktól mentes funkciók kiépítésében, így könnyebb fenntartani a rendszereket - néhány más előnnyel együtt.
Ebben a bejegyzésben többet fogok mondani a funkcionális programozásról és néhány fontos fogalomról, sok kód példával.
Ez a cikk a Clojure programozási nyelv példaként használja a funkcionális programozás magyarázatát. Ha nem kedveli a LISP-típusú nyelveket, ugyanazt a bejegyzést JavaScriptben is közzétettem. Vessen egy pillantást: Funkcionális programozási alapelvek Javascriptben
Mi a funkcionális programozás?
A funkcionális programozás olyan programozási paradigma - a számítógépes programok struktúrájának és elemeinek felépítésének stílusa -, amely a számítást matematikai függvények kiértékelésének tekinti, és elkerüli a változó állapotú és a mutábilis adatokat - WikipediaTiszta funkciók

Az első alapvető fogalom, amelyet akkor tanulunk meg, amikor meg akarjuk érteni a funkcionális programozást, a tiszta függvények . De mit is jelent ez valójában? Mitől tiszta a függvény?
Tehát honnan tudjuk, hogy egy függvény-e pure
vagy sem? Itt van a tisztaság nagyon szigorú meghatározása:
- Ugyanazokat az eredmény, ha az adott ugyanazokat az érveket (ez is nevezik
deterministic
) - Nem okoz megfigyelhető mellékhatásokat
Ugyanazt az eredményt adja vissza, ha ugyanazokat az érveket adják meg neki
Képzelje el, hogy egy olyan függvényt szeretnénk megvalósítani, amely kiszámítja egy kör területét. Egy tisztátalan függvény kap radius
paraméterként, majd kiszámítja radius * radius * PI
. A Clojure-ban az operátor az első, így radius * radius * PI
válik (* radius radius PI)
:
Miért ez a tisztátalan funkció? Egyszerűen azért, mert olyan globális objektumot használ, amelyet nem adtak át a függvény paramétereként.
Most képzelje el, hogy egyes matematikusok azzal érvelnek, hogy az PI
érték valóban az, 42
és megváltoztatják a globális objektum értékét.
A tisztátalan függvényünk most 10 * 10 * 42
= 4200
. Ugyanarra a paraméterre ( radius = 10
) más eredményt kapunk. Javítsuk ki!
TA-DA? Most mindig átadjuk a P I
értéket paraméterként a függvénynek. Tehát most csak a függvényhez továbbított paraméterekhez férünk hozzá. Nincs external object.
- A
radius = 10
& paramétereknélPI = 3.14
mindig ugyanaz az eredmény lesz:314.0
- A
radius = 10
& paramétereknélPI = 42
mindig ugyanaz az eredmény lesz:4200
Fájlok olvasása
Ha a függvényünk külső fájlokat olvas, akkor ez nem tiszta funkció - a fájl tartalma megváltozhat.
Véletlen számgenerálás
Bármely függvény, amely véletlenszám-generátorra támaszkodik, nem lehet tiszta.
Nem okoz megfigyelhető mellékhatásokat
A megfigyelhető mellékhatások közé tartozik egy globális objektum vagy egy referenciaként átadott paraméter módosítása.
Most egy függvényt szeretnénk megvalósítani, hogy egy egész értéket kapjon, és az 1-gyel növelt értéket adja vissza.
Megvan az counter
értéke. A tisztátalan függvényünk megkapja ezt az értéket, és újra hozzárendeli a számlálót az 1-gyel növelt értékkel.
Megfigyelés : a funkcionális programozásban a mutabilitás nem javasolt.
Módosítjuk a globális objektumot. De hogyan csinálnánk pure
? Csak adja vissza az 1-gyel növelt értéket. Egyszerű.
Lásd, hogy a tiszta függvényünk increase-counter
2-t ad vissza, de az counter
értéke továbbra is ugyanaz. A függvény a növelt értéket adja vissza a változó értékének megváltoztatása nélkül.
Ha betartjuk ezt a két egyszerű szabályt, akkor könnyebben megértjük programjainkat. Most minden funkció elszigetelt és nem képes befolyásolni a rendszer más részeit.
A tiszta funkciók stabilak, következetesek és kiszámíthatók. Ugyanazon paraméterek mellett a tiszta függvények mindig ugyanazt az eredményt adják. Nem kell olyan helyzetekre gondolnunk, amikor ugyanazon paraméter különböző eredményekkel jár - mert ez soha nem fog megtörténni.
A tiszta funkciók előnyei
A kódot mindenképpen könnyebb tesztelni. Semmit sem kell gúnyolnunk. Tehát a tiszta funkciókat különböző kontextusokkal tesztelhetjük:
- Adott paraméter
A
→ várjuk, hogy a függvény visszaadja az értéketB
- Adott paraméter
C
→ várjuk, hogy a függvény visszaadja az értéketD
Egyszerű példa egy olyan funkció, amely megkapja a számok gyűjteményét, és elvárja, hogy növelje a gyűjtemény minden elemét.
Megkaptuk a numbers
gyűjteményt, map
a inc
függvénnyel használjuk az egyes számok növelését, és a növekményes számok új listájának visszaküldését.
A input
[1 2 3 4 5]
várhatóan az output
lenne [2 3 4 5 6]
.
Állandóság
Idővel nem változik, vagy nem lehet megváltoztatni.
Ha az adatok megváltoztathatatlanok, azok állapota nem változhatlétrehozása után. Ha módosíthatatlan objektumot szeretne megváltoztatni, akkor nem. Ehelyett létrehoz egy új objektumot az új értékkel.
A Javascriptben általában a for
ciklust használjuk . Ez a következő for
állítás tartalmaz néhány változtatható változót.
Minden iterációnál megváltoztatjuk a i
és az sumOfValue
állapotot . De hogyan kezeljük a mutációt az iterációban? Rekurzió! Vissza a Clojure-hoz!
Tehát itt van az a sum
függvény, amely numerikus értékek vektorát fogadja. A recur
ugrik vissza a loop
amíg megkapjuk a vektor üres (a rekurzió base case
). Minden „iterációhoz” hozzáadjuk az értéket az total
akkumulátorhoz.
Rekurzióval megtartjuk a változókatváltozhatatlan.
Megfigyelés : Igen! Használhatjuk reduce
ezt a funkciót. Ezt látni fogjuk a Higher Order Functions
témában.
Nagyon gyakori az objektum végső állapotának felépítése is . Képzelje el, hogy van egy karakterláncunk, és ezt a karakterláncot a-vá akarjuk alakítani url slug
.
A rubin OOP-ban létrehoznánk egy osztályt, mondjuk UrlSlugify
. Ennek az osztálynak lesz egy slugify!
módja a karakterlánc bemenetének a-vá alakítására url slug
.
Gyönyörű! Megvalósult! Itt meg kell adnunk a programozást, amely pontosan megmondja, hogy mit akarunk tenni az egyes slugify
folyamatokban - először kisbetűvel írjuk be, majd távolítsuk el a haszontalan szóközöket, végül pedig a fennmaradó fehér helyeket kötőjellel helyettesítsük.
De ebben a folyamatban a bemeneti állapotot mutáljuk.
Ezt a mutációt kezelhetjük függvényösszetétel vagy függvényláncolással. Más szavakkal, egy függvény eredményét felhasználjuk bemenetként a következő függvényhez, az eredeti bemeneti karakterlánc módosítása nélkül.
Itt van:
trim
: eltávolítja a szóközt a karakterlánc mindkét végénlower-case
: a karakterláncot kisbetűvé alakítjareplace
: az egyezés összes példányát helyettesíti egy adott karakterláncban
Kombináljuk mindhárom funkciót, és meg tudjuk "slugify"
csinálni a karakterláncunkat.
Ha a funkciók kombinálásáról beszélünk, akkor a comp
funkcióval mindhárom funkciót összeállíthatjuk. Lássuk:
Referenciális átláthatóság

Vezessünk be egy square function
:
Ennek a (tiszta) függvénynek mindig ugyanaz a kimenete lesz, ugyanazt a bemenetet kapva.
A „2” átadása a square function
végrendelet paramétereként mindig a 4. értéket adja vissza. Tehát most (square 2)
a 4-et helyettesíthetjük . Ennyi! A mi feladatunk az referentially transparent
.
Alapvetően, ha egy függvény következetesen ugyanazt az eredményt adja ugyanarra a bemenetre, akkor referenciálisan átlátszó.
tiszta funkciók + megváltoztathatatlan adatok = referenciális átláthatóság
Ezzel a koncepcióval remek dolog, amit tehetünk, ha megjegyezzük a funkciót. Képzelje el, hogy rendelkezünk ezzel a funkcióval:
Az (+ 5 8)
egyenlő 13
. Ez a funkció mindig azt eredményezi 13
. Tehát ezt megtehetjük:
És ez a kifejezés mindig azt eredményezi 16
. Helyettesíthetjük a teljes kifejezést numerikus állandóval, és megjegyezhetjük.
Első osztályú entitásként funkcionál

A funkciók első osztályú entitásként való elgondolása az, hogy a függvényeket szintén értékként kezeljük, és adatokként használjuk.
A Clojure-ban gyakran használják defn
a függvények meghatározására, de ez csak a szintaktikai cukor (def foo (fn ...))
. fn
magát a függvényt adja vissza. defn
a-t ad vissza, var
amely egy függvényobjektumra mutat.
Az első osztályú entitásokként funkcionálhatnak:
- állandókból és változókból hivatkozhat rá
- adja át paraméterként más funkcióknak
- adja vissza más funkciók eredményeként
Az elképzelés az, hogy a függvényeket értékként kezeljük, és olyan funkciókat adunk át, mint az adatok. Így kombinálhatjuk a különböző funkciókat, hogy új funkciókat hozzunk létre új viselkedéssel.
Képzelje el, hogy van egy olyan függvényünk, amely két értéket összegez, majd megduplázza az értéket. Valami ilyesmi:
Most egy olyan függvény, amely kivonja az értékeket és a kettőt adja vissza:
Ezeknek a függvényeknek hasonló logikájuk van, de a különbség az operátorok függvénye. Ha a függvényeket értékként kezelhetjük, és ezeket argumentumként adhatjuk meg, akkor létrehozhatunk egy olyan függvényt, amely megkapja az operátor függvényt, és felhasználhatja azt a függvényünkön belül. Építsük meg!
Kész! Most van egy f
érvünk, és felhasználjuk a a
és b
. Átadtuk a +
és a -
függvényeket, hogy összeírjuk a double-operator
függvénnyel és új viselkedést hozzunk létre.
Magasabb rendű funkciók
Ha magasabb rendű függvényekről beszélünk, akkor olyan funkciókat értünk, amelyek:
- - egy vagy több függvényt vesz fel argumentumként, vagy
- függvényt ad eredményül
A double-operator
fent megvalósított függvény egy magasabb rendű függvény, mert argumentumként operátorfüggvényt vesz fel és használja.
Ön valószínűleg már hallott filter
, map
és reduce
. Vessünk egy pillantást ezekre.
Szűrő
Adott gyűjteményt szeretnénk egy attribútum alapján szűrni. A szűrő függvény arra számít, hogy a true
vagy az false
érték meghatározza, hogy az elemet be kell- e vonni az eredménygyűjteménybe. Alapvetően, ha a visszahívási kifejezés az true
, akkor a szűrő funkció tartalmazza az elemet az eredménygyűjteményben. Ellenkező esetben nem fog.
Egyszerű példa, amikor egész számok gyűjteménye van, és csak a páros számokat akarjuk.
Kötelező megközelítés
A Javascript használatának elengedhetetlen módja:
- hozzon létre egy üres vektort
evenNumbers
- iterál a
numbers
vektor felett - tolja a páros számokat a
evenNumbers
vektorhoz
Használhatjuk a filter
magasabb rendű függvényt a even?
függvény fogadására , és visszaadhatunk páros számok listáját:
Egy érdekes probléma, amelyet a Hacker Rank FP Path-on megoldottam, a Filter Array probléma volt . A probléma ötlete az, hogy kiszűr egy adott egész tömböt, és csak azokat az értékeket adja ki, amelyek kisebbek, mint egy megadott érték X
.
A probléma elengedhetetlen Javascript-megoldása:
Pontosan elmondjuk, hogy mit kell tennünk a funkciónknak - ismételjük meg a gyűjteményt, hasonlítsuk össze a gyűjtemény aktuális elemét x
, és toljuk ezt az elemet resultArray
a feltétel teljesítéséhez.
Deklaratív megközelítés
De szeretnénk egy deklaratívabb módszert a probléma megoldására, és a filter
magasabb rendű függvény használatát is.
A deklaratív Clojure megoldás valami ilyesmi lehet:
Ez a szintaxis eleve kissé furcsának tűnik, de könnyen érthető.
#(> x
A%) csak egy névtelen függvény, amely megkapja e
s x-t és összehasonlítja a collectio minden elemével n
. A% a névtelen függvény paraméterét jelenti - ebben az esetben a t he fil
ter belsejében lévő aktuális elemet .
Térképekkel is megtehetjük. Képzelje el, hogy van egy térképünk az emberekről a name
és age
. És csak egy meghatározott életkor felettieket akarunk szűrni, ebben a példában a 21 évnél idősebbeket.
A kód összefoglalása:
- van egy listánk emberekről (és
name
ésage
). - megvan a névtelen függvény
#(< 21 (:age
%)). Ne feledje, hogy th
e% a gyűjtemény jelenlegi elemét képviseli? Nos, a gyűjtemény eleme egy néptérkép. Hado (:age {:name "TK" :age 2
6}), akkore,
ebben az esetben a 26 éves korértéket adja vissza . - minden embert szűrünk e névtelen funkció alapján.
Térkép
A térkép ötlete egy gyűjtemény átalakítása.
Amap
módszer úgy alakít át egy gyűjteményt, hogy egy függvényt alkalmaz az összes elemére, és a visszaadott értékekből egy új gyűjteményt épít.
Nézzük meg ugyanazt a people
gyűjteményt fent. Most nem akarunk szűrni „kor felett”. Csak egy húrok listáját akarjuk, ilyesmi TK is 26 years old
. Tehát az utolsó karakterlánc lehet a gyűjtemény egyes elemeinek :name is :age years old
hol :name
és :age
attribútuma people
.
Javascript nélkülözhetetlen módon ez a következő lenne:
Deklaratív Clojure módon ez lenne:
Az egész elképzelés az, hogy egy adott gyűjteményt új kollekcióvá alakítsunk át.
Egy másik érdekes Hacker Rank probléma a frissítési lista probléma volt . Csak egy adott gyűjtemény értékeit szeretnénk frissíteni abszolút értékeikkel.
Például a bemenetnek [1 2 3 -4 5]
a kimenetnek kell lennie [1 2 3 4 5]
. Az abszolút értéke -4
van 4
.
Egyszerű megoldás az egyes gyűjteményértékek helyben történő frissítése lenne.
A Math.abs
függvény segítségével átalakítjuk az értéket abszolút értékévé, és elvégezzük a helyben történő frissítést.
Ez a megoldás megvalósításának nem funkcionális módja.
Először megismertük a változhatatlanságot. Tudjuk, mennyire fontos a változtathatatlanság, hogy következetesebbé és kiszámíthatóbbá tegyük funkcióinkat. Az ötlet egy új, minden abszolút értéket tartalmazó gyűjtemény felépítése.
Másodszor, miért nem használhatja map
itt az összes adat "átalakítását"?
Az első ötletem az volt, hogy olyan to-absolute
funkciót építsek fel, amely csak egy értéket kezel.
Ha negatív, akkor pozitív értékben (abszolút értékben) akarjuk átalakítani. Ellenkező esetben nem kell átalakítanunk.
Most, hogy tudjuk, hogyan kell elvégezni absolute
egy értéket, használhatjuk ezt a függvényt arra, hogy argumentumként átadjuk a map
függvénynek. Emlékszel arra, hogy a higher order function
a megkapja a függvényt argumentumként és használhatja? Igen, a térkép képes rá!
Azta. Olyan gyönyörű! ?
Csökkentse
A redukció ötlete egy függvény és egy gyűjtemény megkapása, valamint az elemek kombinálásával létrehozott érték visszaadása.
Az emberek általánosan elterjedt példa a megrendelés teljes összegének megszerzése. Képzelje el, hogy egy bevásárló webhelyen volt. Ön megadta Product 1
, Product 2
, Product 3
és Product 4
a kosárba (sorrendben). Most a kosár teljes összegét szeretnénk kiszámítani.
Feltétlenül megismételjük a rendelési listát, és az egyes termékeket összeadjuk a teljes összeggel.
Használatával reduce
felépíthetünk egy függvényt a (z) kezelésére, amount sum
és argumentumként átadhatjuk a reduce
függvénynek.
Itt van az shopping-cart
a funkció, sum-amount
amely fogadja az áramot total-amount
, és az current-product
objektum sum
nekik.
A get-total-amount
funkció használható reduce
a shopping-cart
segítségével a sum-amount
kiindulva 0
.
A teljes összeg megszerzésének másik módja a komponálás map
és reduce
. Mit akarok ezzel mondani? Használhatjuk map
arra, hogy az értéket értékek shopping-cart
gyűjteményévé alakítsuk át amount
, majd csak a reduce
függvényt használjuk a +
funkcióval.
A get-amount
fogadja a termék objektumot, és csak az amount
értéket adja vissza . Tehát itt van az [10 30 20 60]
. Ezután az reduce
összes elemet összeadja az összesítéssel. Gyönyörű!
Megnéztük, hogyan működnek az egyes magasabb rendű függvények. Szeretnék mutatni egy példát arra, hogyan állíthatjuk össze mindhárom függvényt egy egyszerű példában.
Beszél shopping cart
, képzeljük mi ezt a listát a termékek a sorrendben:
Azt akarjuk, hogy a bevásárlókosárban található összes könyv összege legyen. Egyszerű a dolog. Az algoritmus?
- szűrés könyvtípus szerint
- transzformálja a bevásárlókosarat a térkép segítségével összegösszeggé
- kombinálja az összes elemet összeadva redukcióval
Kész! ?
Erőforrások
Rendeztem néhány forrást, amit olvastam és tanulmányoztam. Megosztom azokat, amelyeket igazán érdekesnek találtam. További forrásokért keresse fel a Funkcionális programozás Github tárhelyemet .
- Rubin specifikus források
- Javascript-specifikus források
- Clojure-specifikus források
Bevezetések
- FP tanulása JS-ben
- Intro do FP Python-nal
- Az FP áttekintése
- Gyors bevezető a funkcionális JS-hez
- Mi az FP?
- Funkcionális programozási szakszó
Tiszta funkciók
- Mi a tiszta funkció?
- Tiszta funkcionális programozás 1
- Tiszta funkcionális programozás 2
Változhatatlan adatok
- Változhatatlan DS a funkcionális programozáshoz
- Miért a közös megváltoztatható állapot minden gonosz gyökere
- Strukturális megosztás a Clojure-ban: 1. rész
- Strukturális megosztás a Clojure-ban: 2. rész
- Strukturális megosztás a Clojure-ban: 3. rész
- Strukturális megosztás a Clojure-ban: utolsó rész
Magasabb rendű funkciók
- Beszédes JS: magasabb rendű funkciók
- Szórakoztató szórakoztató funkció szűrő
- Fun fun fun Map
- Fun fun fun function Basic Reduce
- Fun fun function Advanced Reduce
- Clojure magasabb rendű funkciók
- Tisztán funkciós szűrő
- Tisztán funkcionális térkép
- Tisztán funkcionális csökkentés
Nyilatkozatos programozás
- Deklaratív programozás vs kötelező
Ez az!
Hé emberek, remélem, jól éreztétek magatokat ezzel a bejegyzéssel, és remélem, hogy itt sokat tanultatok! Ez volt a kísérletem arra, hogy megosszam, amit tanulok.
Itt található a lerakat a cikk összes kódjával .
Gyere velem tanulni. Az erőforrásokat és a kódomat megosztom ebben a Learning Functional Programming adattárban .
Remélem, láttál itt valami hasznosat számodra. És legközelebb találkozunk! :)
Saját Twitter és Github. ☺
TK.