Funkcionális programozás Android fejlesztők számára - 1. rész

Az utóbbi időben sok időt töltöttem az Elixir megtanulásával, egy fantasztikus funkcionális programozási nyelvvel, amely barátságos a kezdők számára.

Ez elgondolkodtatott: miért ne használhatnánk a funkcionális világ néhány fogalmát és technikáját az Android programozásában?

Amikor a legtöbb ember hallja a Funkcionális programozás kifejezést , azokra a Hacker News bejegyzésekre gondol, amelyek a monádokról, a magasabb rendű funkciókról és az absztrakt adattípusokról csapkodnak. Úgy tűnik, misztikus univerzum, amely távol áll a napi programozó fáradalmaitól, és csak a Númenor birodalmából leszármazott legerősebb hackerek számára van fenntartva.

Hát ezt cseszd meg ! Azért vagyok itt, hogy elmondjam neked, hogy te is megtanulhatod. Te is használhatod. Te is létrehozhatsz gyönyörű alkalmazásokat vele. Elegáns, olvasható kódbázissal rendelkező és kevesebb hibát tartalmazó alkalmazások.

Üdvözöljük az Android fejlesztők funkcionális programozásában (FP). Ebben a sorozatban megtanuljuk az FP alapjait és azt, hogy miként használhatjuk őket a jó öreg Java-ban és az új, fantasztikus Kotlinban. Az elképzelés az, hogy a fogalmakat a gyakorlatiasságra alapozzuk, és kerüljük a lehető legtöbb akadémiai szaknyelvet.

Az FP hatalmas téma. Csak azokat a fogalmakat és technikákat fogjuk megtanulni, amelyek hasznosak az Android-kód írásához. Meglátogathatunk néhány olyan koncepciót, amelyeket a teljesség kedvéért közvetlenül nem használhatunk, de megpróbálom az anyagot a lehető legrelevánsabbnak tartani.

Kész? Gyerünk.

Mi az a funkcionális programozás, és miért használjam?

Jó kérdés. A Funkcionális programozás kifejezés számos programozási koncepció ernyője, amelyeknek a moniker nem igazán tesz igazat. Alapjában véve ez egy olyan programozási stílus, amely a programokat a matematikai függvények értékeléseként kezeli, és elkerüli a mutálható állapotot és a mellékhatásokat (ezekről hamarosan beszélünk).

Alapjában az FP hangsúlyozza:

  • Deklaratív kód - A programozóknak aggódniuk kell a mitől, és hagyniuk kell , hogy a fordító és a futásidő hogyan .
  • Magyarázat - A kódnak a lehető legnyilvánvalóbbnak kell lennie. Különösen mellékhatásokel kell különíteni a meglepetések elkerülése érdekében. Az adatáramlást és a hibakezelést kifejezetten meghatározzák, és kerülik az olyan konstrukciókat, mint a GOTO utasítások és a kivételek, mivel ezek váratlan állapotokba sodorhatják az alkalmazást.
  • Egyidejűség - A legtöbb funkcionális kód alapértelmezés szerint egyidejű, a funkcionális tisztaságnak nevezett koncepció miatt . Úgy tűnik, hogy az általános megegyezés szerint ez a tulajdonság különösen a funkcionális programozás népszerűségének növekedését okozza, mivel a CPU magok nem minden évben gyorsulnak, mint korábban (lásd Moore törvényét), és a programjainkat egyidejűbbé kell tennünk a kihasználás érdekében többmagos architektúrák.
  • Magasabb rendű függvények - A függvények első osztályú tagok, csakúgy, mint az összes többi primitív nyelv. Átadhatja a függvényeket úgy, mint egy stringet vagy egy int-et.
  • Változatlanság - A változókat inicializálásuk után nem szabad módosítani. Ha egy dolog létrejön, akkor ez örökre. Ha azt akarja, hogy megváltozzon, akkor létrehoz egy új dolgot. Ez a explicit tanúsítás és a mellékhatások elkerülésének másik aspektusa. Ha tudja, hogy egy dolog nem változhat, akkor sokkal jobban bízik az állapotában, amikor használja.

Deklaratív, explicit és egyidejű kód, amelyről könnyebben lehet érvelni, és célja a meglepetések elkerülése? Remélem, felkeltettem az érdeklődését.

A sorozat ezen első részében kezdjük az FP néhány alapvető fogalmával: Tisztaság , Mellékhatások és Rendelés .

Tiszta funkciók

A függvény akkor tiszta, ha a kimenete csak a bemenettől függ, és nincs mellékhatása (a mellékhatásokról kicsit később beszélünk). Lássunk egy példát, igaz?

Fontolja meg ezt az egyszerű függvényt, amely két számot ad hozzá. Az egyik számot felolvassa egy fájlból, a másik számot pedig paraméterként adja meg.

Jáva

int add(int x) { int y = readNumFromFile(); return x + y;}

Kotlin

fun add(x: Int): Int { val y: Int = readNumFromFile() return x + y}

Ez a függvény kimenete nem csak a bemenetétől függ. Attól függően, hogy a readNumFromFile () mit ad vissza, különböző kimenetekkel rendelkezhet ugyanarra az x értékre . Azt mondják, hogy ez a funkció tisztátalan .

Konvertáljuk tiszta funkcióvá.

Jáva

int add(int x, int y) { return x + y;}

Kotlin

fun add(x: Int, y: Int): Int { return x + y}

Most a funkció kimenete csak a bemeneteitől függ. Adott x és y esetén a függvény mindig ugyanazt a kimenetet adja vissza. Erről a funkcióról most azt mondják, hogy tiszta . A matematikai függvények is ugyanúgy működnek. A matematikai függvények kimenete csak a bemeneteitől függ - ezért a funkcionális programozás sokkal közelebb áll a matematikához, mint a megszokott szokásos programozási stílus.

PS Az üres bemenet továbbra is bemenet. Ha egy függvény nem vesz be bemenetet, és minden alkalommal ugyanazt az állandót adja vissza, akkor is tiszta.

PPS Az a tulajdonság, hogy mindig ugyanazt a kimenetet adja vissza egy adott bemenetre, referencia átlátszóságnak is nevezik, és láthatja, hogy a tiszta függvényekről beszél.

Mellékhatások

Fedezzük fel ezt a fogalmat ugyanazzal az összeadási függvény példával. Módosítjuk az Összeadás függvényt, hogy az eredményt fájlba is írjuk.

Jáva

int add(int x, int y) { int result = x + y; writeResultToFile(result); return result;}

Kotlin

fun add(x: Int, y: Int): Int { val result = x + y writeResultToFile(result) return result}

Ez a függvény most egy fájlba írja a számítás eredményét. azaz most módosítja a külvilág állapotát. Ennek a funkciónak azt mondják, hogy mellékhatása van, és már nem tiszta funkció.

Minden kódnak, amely módosítja a külvilág állapotát - változót vált, fájlba ír, DB-be ír, valamit töröl, stb. - állítólag mellékhatása van.

Az FP-ben kerülik azokat a funkciókat, amelyeknek mellékhatásai vannak, mert azok már nem tiszták és a történelmi kontextustól függenek . A kódkörnyezet nem önálló. Ez sokkal nehezebben indokolja őket.

Tegyük fel, hogy olyan gyorsítótár-függő kóddarabot írsz. Most a kódod kimenete attól függ, hogy valaki írt-e a gyorsítótárba, mit írtak bele, mikor írták, ha érvényesek az adatok stb. a gyorsítótár közül ez függ. Ha ezt kiterjeszti az összes többi dologra, amelytől függ az alkalmazás - hálózat, adatbázis, fájlok, felhasználói bevitel stb., Akkor nagyon nehéz megtudni, hogy mi folyik pontosan, és mindezt egyszerre a fejébe illeszteni.

Ez azt jelenti, hogy akkor nem használunk hálózatot, adatbázisokat és gyorsítótárakat? Természetesen nem. A végrehajtás végén azt szeretné, hogy az alkalmazás tegyen valamit. Az Android-alkalmazások esetében ez általában a felhasználói felület frissítését jelenti, hogy a felhasználó valóban kaphasson valami hasznosat az alkalmazásunkból.

Az FP legnagyobb ötlete nem a mellékhatások teljes mellőzése, hanem azok megfékezése és elszigetelése. Ahelyett, hogy alkalmazásunk tele lenne olyan funkciókkal, amelyeknek mellékhatásai vannak, a mellékhatásokat a rendszerünk szélére toljuk, hogy azok a lehető legkevesebb hatást fejtsék ki, megkönnyítve ezzel az alkalmazásunk okoskodását. Erről részletesen beszélünk, amikor a sorozat későbbi részében funkcionális architektúrát tárunk fel alkalmazásaink számára.

Rendelés

Ha van egy csomó tiszta funkciónk, amelyeknek nincs mellékhatása, akkor a végrehajtás sorrendje lényegtelenné válik.

Tegyük fel, hogy van egy olyan függvényünk, amely 3 tiszta függvényt hív meg belsőleg:

Jáva

void doThings() { doThing1(); doThing2(); doThing3();}

Kotlin

fun doThings() { doThing1() doThing2() doThing3()}

Pontosan tudjuk, hogy ezek a függvények nem függnek egymástól (mivel az egyik kimenete nem a másik bemenete), és azt is tudjuk, hogy semmit sem fognak megváltoztatni a rendszerben (mivel tiszták). Ez teljesen felcserélhetővé teszi a végrehajtás sorrendjét.

A végrehajtás sorrendje átkeverhető és optimalizálható a független tiszta funkciókhoz. Megjegyzendő, hogy ha a bemeneti doThing2 () eredménye volt doThing1 () , majd ezek is végre kell hajtani annak érdekében, de doThing3 () még lehet újra megrendelt előtt végrehajtandó doThing1 ().

Mit hoz ez a megrendelő ingatlan mégis? Párhuzamosság, ez az! Ezeket a funkciókat 3 különálló CPU magon futtathatjuk, anélkül, hogy aggódnunk kellene bármi felcsavarása miatt!

Sok esetben az olyan fejlett tiszta funkcionális nyelvű fordítók, mint a Haskell, meg tudják mondani, ha formálisan elemzik a kódot, hogy egyidejű-e vagy sem, és megakadályozhatják, hogy holtpontokkal, versenykörülményekkel és hasonlókkal lődd be magad. Ezek a fordítók elméletileg automatikusan párhuzamosíthatják a kódodat (ez valójában nem létezik egyetlen olyan fordítóban sem, amelyet jelenleg ismerek, de a kutatás folyamatban van).

Még akkor is, ha a fordító nem ezeket a dolgokat nézi, programozóként, nagyon jó, ha csak a függvény aláírásaival megtudhatjuk mondani, hogy a kód párhuzamos-e, és elkerülhetők a csúnya szálak hibái, amelyek megpróbálják párhuzamosítani a kötelező kódot, amely tele lehet rejtve mellékhatások.

Összegzés

Remélem, hogy ez az első rész érdekes volt az FP-vel kapcsolatban. A tiszta, mellékhatásoktól mentes funkciók sokkal könnyebbé teszik a kóddal való indoklást, és ezek az első lépés a párhuzamosság elérése felé.

Mielőtt azonban a párhuzamossághoz jutnánk, meg kell tanulnunk a változhatatlanságot . Éppen ezt tesszük ennek a sorozatnak a 2. részében, és megnézzük, hogy a tiszta funkciók és a megváltoztathatatlanság hogyan segíthet nekünk egyszerű és könnyen érthető párhuzamos kód megírásában, zárak és mutexek igénybevétele nélkül.

Olvassa el a következőt

Funkcionális programozás Android fejlesztőknek - 2. rész

Ha még nem olvasta el az 1. részt, kérjük, olvassa el itt: medium.com

Ha tetszett, kattintson a? lent. Észreveszem mindegyiket, és hálás vagyok mindegyikért.

Ha többet szeretne megtudni a programozásról, kövessen engem, hogy értesítést kapjon, amikor új bejegyzéseket írok.