Bevezetés az F funkcionális programozási fogalmaiba#

A funkcionális programozás a programozás olyan stílusa, amely a függvények és a nem módosítható adatok használatát hangsúlyozza. A gépelt funkcionális programozás akkor működik, ha a funkcionális programozás statikus típusokkal, például F#-tal van kombinálva. A funkcionális programozásban általában a következő fogalmak vannak kiemelve:

  • Függvények elsődleges szerkezetként
  • Utasítások helyett kifejezések
  • Nem módosítható értékek változókon
  • Deklaratív programozás imperatív programozáson keresztül

Ebben a sorozatban az F# használatával megismerheti a funkcionális programozás fogalmait és mintáit. Az út során néhány F#-t is megtanulhat.

Terminológia

A funkcionális programozás, mint más programozási paradigmák, olyan szókészlettel rendelkezik, amelyet végül meg kell tanulnia. Íme néhány gyakori kifejezés, amelyeket mindig látni fog:

  • Függvény – A függvény olyan szerkezet, amely kimenetet hoz létre bemenet esetén. Formálisabban leképez egy elemet az egyik halmazból a másikba. Ezt a formalizmust számos módon emelik a betonba, különösen akkor, ha olyan függvényeket használnak, amelyek adatgyűjteményeken működnek. Ez a funkcionális programozás legalapvetőbb (és legfontosabb) fogalma.
  • Kifejezés – A kifejezés olyan kódszerkezet, amely értéket hoz létre. Az F#-ban ezt az értéket kötöttnek vagy explicit módon figyelmen kívül kell hagyni. Egy kifejezés triviálisan helyettesíthető függvényhívással.
  • Tisztaság – A tisztaság egy függvény olyan tulajdonsága, hogy a visszatérési értéke mindig ugyanaz ugyanazon argumentumok esetében, és a kiértékelése nem jár mellékhatások nélkül. A tiszta függvények teljes mértékben az argumentumaitól függnek.
  • Hivatkozási átláthatóság – A hivatkozási átlátszóság olyan kifejezések tulajdonsága, amelyek a program viselkedésének befolyásolása nélkül helyettesíthetők a kimenetükkel.
  • Nem módosítható – A megváltoztathatatlanság azt jelenti, hogy egy érték nem módosítható helyben. Ez ellentétben áll a változókkal, amelyek a helyén változhatnak.

Példák

Az alábbi példák ezeket az alapvető fogalmakat mutatják be.

Functions

A funkcionális programozás leggyakoribb és legalapvetőbb felépítése a függvény. Íme egy egyszerű függvény, amely 1-et ad hozzá egy egész számhoz:

let addOne x = x + 1

A típusaadék a következő:

val addOne: x:int -> int

Az aláírás olvasható a következőként: "addOne elfogad egy int elnevezettet x , és létrehoz egy "-t int. Formálisabban addOne az egész számok halmazából az egész számok halmaza és az egész számok halmaza közötti érték megfeleltetése . A -> jogkivonat ezt a leképezést jelöli. Az F#-ban általában a függvény-aláírást tekintheti meg, hogy képet kapjon arról, hogy mit csinál.

Miért fontos az aláírás? A gépelt funkcionális programozásban a függvény megvalósítása gyakran kevésbé fontos, mint a tényleges típusadektálás! Az a tény, hogy addOne az 1 értéket hozzáadja egy egész számhoz, futásidőben érdekes, de egy program létrehozásakor az a tény, hogy elfogadja és visszaadja int a függvényt, azt jelzi, hogyan fogja ténylegesen használni ezt a függvényt. Továbbá, ha helyesen használja ezt a függvényt (a típus-aláírás tekintetében), a problémák diagnosztizálása csak a addOne függvény törzsén belül végezhető el. Ez a gépelt funkcionális programozás mögötti lendület.

Kifejezések

A kifejezések olyan szerkezetek, amelyek kiértékelése értékként történik. A műveletet végrehajtó állításokkal ellentétben a kifejezések arra is gondolhatnak, hogy olyan műveletet hajtanak végre, amely visszaad egy értéket. A kifejezéseket szinte mindig a funkcionális programozásban használják utasítások helyett.

Vegye figyelembe az előző függvényt. addOne A törzs addOne egy kifejezés:

// 'x + 1' is an expression!
let addOne x = x + 1

Ennek a kifejezésnek az eredménye határozza meg a függvény eredménytípusát addOne . A függvényt alkotó kifejezés például egy másik típusra módosítható, például string:

let addOne x = x.ToString() + "1"

A függvény aláírása most a következő:

val addOne: x:'a -> string

Mivel az F#-ban bármilyen típus meghívhatóToString(), a típus x általánossá (automatikus általánosításnak) lett nevezve, és az eredményül kapott típus egy string.

A kifejezések nem csak a függvények testei. Olyan kifejezések is lehetnek, amelyek máshol használt értéket hoznak létre. Gyakori megoldás a következő if:

// Checks if 'x' is odd by using the mod operator
let isOdd x = x % 2 <> 0

let addOneIfOdd input =
    let result =
        if isOdd input then
            input + 1
        else
            input

    result

A if kifejezés létrehoz egy .result Vegye figyelembe, hogy teljes egészében kihagyhatja result a kifejezést, így a if kifejezés a addOneIfOdd függvény törzse lesz. A kifejezésekkel kapcsolatban fontos megjegyezni, hogy értéket hoznak létre.

Van egy speciális típus, amelyet akkor használunk, unitha nincs mit visszaadni. Vegyük például ezt az egyszerű függvényt:

let printString (str: string) =
    printfn $"String is: {str}"

Az aláírás a következőképpen néz ki:

val printString: str:string -> unit

A unit típus azt jelzi, hogy nincs tényleges érték visszaadva. Ez akkor hasznos, ha olyan rutinja van, amelynek "dolgoznia kell", annak ellenére, hogy a munka eredményeként nincs visszaadandó érték.

Ez éles ellentétben áll az imperatív programozással, ahol az egyenértékű if szerkezet egy utasítás, és az értékek előállítása gyakran változók mutációjával történik. A C#-ban például a kód a következőképpen írható:

bool IsOdd(int x) => x % 2 != 0;

int AddOneIfOdd(int input)
{
    var result = input;

    if (IsOdd(input))
    {
        result = input + 1;
    }

    return result;
}

Érdemes megjegyezni, hogy a C# és más C stílusú nyelvek támogatják a ternáris kifejezést, amely lehetővé teszi a kifejezésalapú feltételes programozást.

A funkcionális programozásban ritkán lehet az értékeket utasításokkal mutálni. Bár egyes funkcionális nyelvek támogatják az utasításokat és a mutációt, nem gyakori, hogy ezeket a fogalmakat funkcionális programozásban használják.

Tiszta függvények

Ahogy korábban említettük, a tiszta függvények olyan függvények, amelyek:

  • Mindig ugyanarra az értékre kell kiértékelni ugyanahhoz a bemenethez.
  • Nincsenek mellékhatásai.

Ebben a kontextusban érdemes matematikai függvényeket is átgondolni. A matematikában a függvények csak az argumentumaiktól függnek, és nincsenek mellékhatásaik. A matematikai függvényben f(x) = x + 1a függvény értéke f(x) csak a függvény értékétől xfügg. A funkcionális programozásban a tiszta függvények ugyanúgy működnek.

Tiszta függvény írásakor a függvénynek csak az argumentumaitól kell függenie, és nem szabad olyan műveletet végrehajtania, amely mellékhatást eredményez.

Íme egy példa egy nem tiszta függvényre, mert globális, mutable állapottól függ:

let mutable value = 1

let addOneToValue x = x + value

A addOneToValue függvény egyértelműen nem egyértelmű, mert value bármikor módosítható, hogy az értéke 1-nél eltérő legyen. Ezt a globális értéktől függő mintát el kell kerülni a funkcionális programozásban.

Íme egy másik példa a nem tiszta függvényre, mert mellékhatást hajt végre:

let addOneToValue x =
    printfn $"x is %d{x}"
    x + 1

Bár ez a függvény nem függ globális értéktől, a program kimenetére x írja az értéket. Bár ennek a műveletnek nincs természeténél fogva semmi baja, ez azt jelenti, hogy a függvény nem tiszta. Ha a program egy másik része a programon kívüli dologtól , például a kimeneti puffertől függ, akkor a függvény meghívása hatással lehet a program másik részére.

Az printfn utasítás eltávolítása a függvényt tisztavá teszi:

let addOneToValue x = x + 1

Bár ez a függvény eredendően nem jobb , mint az előző verzió az printfn utasítással, garantálja, hogy ez a függvény csak egy értéket ad vissza. A függvény meghívása tetszőleges számú alkalommal ugyanazt az eredményt eredményezi: csak egy értéket hoz létre. A tisztaság által biztosított kiszámíthatóságra sok funkcionális programozó törekszik.

Módosíthatatlanság

Végül a gépelt funkcionális programozás egyik legalapvetőbb fogalma a megváltoztathatatlanság. Az F#-ban az összes érték alapértelmezés szerint nem módosítható. Ez azt jelenti, hogy nem mutálhatók helyben, kivéve, ha ön kifejezetten mutableként jelöli meg őket.

A gyakorlatban a megváltoztathatatlan értékekkel való munka azt jelenti, hogy megváltoztatja a programozás megközelítését a "Meg kell változtatni valamit", és "új értéket kell létrehoznom".

Ha például 1-et ad hozzá egy értékhez, az azt jelenti, hogy új értéket hoz létre, nem pedig a meglévőt mutálja:

let value = 1
let secondValue = value + 1

Az F#-ban a következő kód nem mutálja a value függvényt; ehelyett egyenlőség-ellenőrzést végez:

let value = 1
value = value + 1 // Produces a 'bool' value!

Egyes funkcionális programozási nyelvek egyáltalán nem támogatják a mutációt. Az F#-ban ez támogatott, de nem ez az értékek alapértelmezett viselkedése.

Ez a fogalom még tovább terjed az adatstruktúrákra. A funkcionális programozásban a nem módosítható adatstruktúrák, például a készletek (és még sok más) eltérő implementációval rendelkeznek, mint amire eredetileg számítani lehetett. Elméletileg egy elem készlethez való hozzáadása nem változtatja meg a készletet, hanem egy új készletet hoz létre a hozzáadott értékkel. A fedelek alatt ezt gyakran egy másik adatstruktúra teszi lehetővé, amely lehetővé teszi az értékek hatékony nyomon követését, hogy az adatok megfelelő ábrázolása eredményeképpen meg lehessen adni.

Az értékekkel és adatstruktúrákkal való munka ezen stílusa kritikus fontosságú, mivel minden olyan művelet kezelésére kényszeríti, amely módosít valamit, mintha az létrehoz egy új verziót. Ez lehetővé teszi, hogy az egyenlőség és az összehasonlíthatóság konzisztens legyen a programokban.

Következő lépések

A következő szakasz részletesen ismerteti a függvényeket, feltárva a funkcionális programozásban használható különböző módszereket.

Az F# -függvények használata mélyen feltárja a függvényeket, és bemutatja, hogyan használhatja őket különböző kontextusokban.

További olvasnivalók

A Thinking Functionally sorozat egy másik nagyszerű forrás a funkcionális programozás megismeréséhez az F#-tal. Gyakorlatias és könnyen olvasható módon ismerteti a funkcionális programozás alapjait, az F# funkcióival szemlélteti a fogalmakat.