A függvények típusának megadása típuskifejezésekkel történik, Curry-formában(Ha Curry-formában gondolkozunk, akkor belátható, hogy lényegében minden operátor egyváltozós. Indok: vegyük pl.: a 3+4 kifejezést. Ezt értelmezhetjük úgy, hogy először vesszük a 3+ -t, ami annyit jelent hogy a majd megadott argumentumhoz hozzáad hármat. Gondoljunk a C vagy C++-ból ismert ++ operátorra, csak itt egy helyett hárommal növelünk. Így, ha megadjuk a (3+)-nak 4-et, akkor a négyet megnöveljük hárommal. Ez a többi operátorral is végiggondolható, ezért mondhatjuk, hogy Curry értelmezése alapján minden operátor egyváltozós.). Ez, ha úgy tetszik, a függvény prototípusa. Ennek azonban nem kell a függvény meghívásának helyén már ismertnek lennie, mivel a Haskell a Hindley-Milner rendszerrel kiszámítja a megfelelő típust - tehát csak annyi a feltétel, hogy a kiszámított típus ne ütközzön a megadottal. Mi több, a prototípust egyáltalán nem is kötelező megadni. Persze jobb, ha mégis kiírjuk, hogy ellenőrizhessük, valóban jól írtuk meg, és jól is használjuk a függvényt.
A Haskell függvények leképezés típusúak, azaz a deklarációjukban legkülső szinten szerepel egy vagy több "->" (pl. Integer -> [Integer] -> String, de nem [Integer -> Integer]). Ha a függvény deklarációjában szerepel sablon (polimorf) típus, akkor sablon (polimorf) függvényről beszélhetünk. Egy függvényt két módon lehet definiálni.
Példa:
vagy a lambda-kalkulushoz közelebbi alakban:
Minden kétváltozós függvény írható infix és nem infix alakban is: a szimbólum azonosítójú függvények nem infix alakja ([függvény]), a szöveges azonosítójú függvények infix alakja pedig `[függvény]`, azaz
Függvényeket többször is lehet definiálni, ezek közül az első megfelelő fog kiértékelődni, ezért figyelni kell rá, hogy a speciális eseteket írjuk előbb. Így pl. a nem negatív dekrementálás:
Ez igazából nem más, mint a már korábban ismertetett case több sorban, tehát az előző definíciót írhatnánk így is:
pl. a Fibonacci sorozat:
ahol a zip a két sorozatból párok sorozatát állítja elő, tehát
Függvények kompozícióját képezhetjük a (.) (infix) operátorral, ez a következő függvény:
A mintaillesztés során a föggvényt helyettesítjük a függvény törzsével, egészen addig amíg el nem érjük a normálformát. A kiértékelési stratégia, azaz a redex-ek (reducible expressions) kiválasztási sorrendje Haskell esetén lusta kiértékelésű. Ez azt jelenti, hogy a kifejezés (rész)eredményét nem feltétlenül kell ismernünk, hogy megkapjuk a végeredményt. Ha egy kifejezés egyik tagjából már következik valami, akkor felesleges a másik tagját kiértékelni, mivel az már nem befolyásolná a végeredményt. Pl.: igaz || (hamis || igaz). Már az első igazból következik, hogy az állítás igaz, független a zárójeles tagtól, ezért az a rész már ki sem értékelődik.
Függvények kiértékelésénél az aktuális paramétert a fordító/interpreter megpróbálja a formális paraméterhez illeszteni, és az első illeszkedő definíciót használja. Ezt, ahogy fent is láthattuk, befolyásolják az őrök, az if/then/else illetve a case kifejezések. Ezenkívül a formális paraméter lehet adatkonstruktort használó kifejezés is, pl. a
Van, hogy kényelmes lenne az egész paraméterre is hivatkozni, ezt az "@" (as) segítségével tehetjük meg, pl.
A "_" mindent helyettesít, ez a wildcard karakter, így pl. a head definíciója:
Ha egy függvényt definiálunk, akkor nem feltétlen kell megadnunk a szintaxisát, a fordító ki fogja következtetni. A Haskellben a felhasználó is definiálhat többszörösen terhelt függvényeket, sőt, a meglévő, többszörösen terhelt függvényeket kiterjesztheti újabb típusokra.
Függvény definíció esetén sokszor a zárójelek elszaporodása csökkenti az átláthatóságot. A Haskellben ezt megelőzően használható a $ jel, ami egy zárójelet takar a sor végéig. Tehát a $ egy szintaktikus cukor, valójában maga is egy infix operátor, amely egy függvényt és egy paramétert vár, és eredménye a függvény(paraméter) kifejezés. Ám mivel precedenciája nagyon alacsony így a fenti hatást is eléri, azaz
kifejezés ekvivalens a következővel:
Mint már említettük a függvények kiértékelése lusta módon történik. Tehát az argumentumok kiértékelését próbálja minél inkább elhalasztani. Arról is volt szó hogy néha hatékonysági szempontból nem megfelelő a lusta kiértékelés. Szerencsére a Haskell lehetőséget ad kérésünkre a mohó kiértékelése. Ehhez használhatjuk a seq függvényt, melynek jelentése:
Ez a függvény ha az a kiértékelés után értelmes eredményt ad végrehajtja a b-t. A megértéshez, és átláthatósághoz be van vezetve a $! mohó kiértékelő operátor
Azaz tehát a $! ha értelmes az argumentum kiértékelése, akkor a függvény eredményét adva vissza, kijátszva ezzel a lustaságot.
A rekurzió az amikor egy függvény közvetlenül, vagy közvetve önmagát hívja (utóbbi esetén kölcsönös rekurzióról beszélhetünk). Rekurzió esetén fontos hogy a függvény esetszétválasztó legyen a paraméterei tekintetében, különben könnyen végtelen rekurzió lehet (általában mintaillesztéssel, őrfeltételekkel, illetve if ..then .. else konstrukcióval érik el). Figyelni kell továbbá hogy ne legyen túl mély, mivel míg imperatív nyelvek esetén az aktivációs rekordok megtöltik a végrehajtási vermet, a funkcionális nyelvek esetén a lusta kiértékelés miatt egyre bővülő kifejezés lehet már túl nagy. Egy egyszerű példa rekurzióra:
Mi is történik a fact 2 hívására?
Itt meg is állhatnánk mondván ezen ismeretekkel már bárki tud rekurzív függvényeket írni. Ami igaz is, de sajnos nem mindenki tud jó rekurzív függvényeket írni, ami hatékonyan bánik a memóriával és a processzoridővel.
Azt nevezzük „vég” rekurzióak (tail recursion) amikor egy függvény paramétere a rekurzív hívás végeredménye. Példa NEM ilyen függvényekre:
Mi lehet a probléma a fenti definíciókkal? Működésük helyes, ám de nézzük a következő lefutást:
Látható hogy a függvény kibontott változata nagyon sokáig él a kibontáshoz képest, nem redukálható vissza, mert sajnos meg kell várnia a rekurzív hívás eredményét, ami szintén erre vár. Miközben szép lassan fogy a memória (például a len 1 millió hosszú listára már stack overflow-al jár), és ráadásul lassú is (len 0,20 sec 20 ezer hosszú listára). Jó lenne valahogy elérni, hogy hamarabb ki lehessen értékelni részformulákat. Erre egy jó módszer hogy összesítő paraméterrel „vég” rekurzióvá alakítjuk a fenti függvényeket:
Mit nyertünk ezzel?
Azaz most jóval hamarabb részeredményekhez jutunk, és nem kell a memóriát a kifejezés tárolására használni, hanem elég csak a részeredményt. Ezáltal gyorsul is a függvény, (például a len 20 ezer hosszú listára 0,02 sec).
Ha valaki figyelt észrevehette hogy még van egy kis csalás. Ugyanis a lusta kiértékelés miatt még az összesítő paraméter valójában a rekurzió végéig nő: (0+3+4+5+6) [jobb esetben a fordító optimalizálására a (+) mohó]. Így érdemes lenne explicit rávenni a mohó kiértékelésre (csak a megváltozott sorok):
Fentebb láthattuk, hogy hatékony rekurziók megírása nem egyszerű feladat. Ám de a standard könyvtárban rengeteg hasznos hatékony rekurzív függvény van, melyek használatával egyszerű és hatékony kódot kapunk. Nem véletlenül szokás a faktoriális példánál profi megoldásnak nevezni a következő egysoros kódot:
Míg imperatív nyelvekben csak a magasabb szintű tárgyalás során kerül sor az alprogramra mutató hivatkozásra, a funkcionális világban az egyik legtermészetesebb dolog, hogy függvényeknek függvény is lehet paramétere (hiszen minden függvény). Ezt nevezzük magasabbrendű függvényeknek. Használatuk, és hasznosságuk a quicksort példáján keresztül:
Ez maga a quicksort algoritmus definíciója. Használatkor csak a neve után írunk egy listát olyan típusú elemekből, melyek az Ord típusosztályba tartoznak (azaz a szükséges műveletek definiáltak). De mi van akkor ha a [„hello”, „World!”] bemenetre hívjuk meg a függvényt? Elsőre meglepő módon felcseréli a sorrendet (ugyanis az szokásos kódolásokban „W” előbb van mint „a”). Lehet hogy ez a programozó célja, de nem biztos, így célszerű lenne valamilyen módon lehetőséget adni a programozónak a döntésre (és a rugalmasságra, ugyanis néha lehet hogy éppen visszafelé sorrend, illetve más rendezés kell). Jobban belegondolva a rendezéshez (lásd Ord típusosztályban a compare függvény) csak egy Ordering kimenetű (LT, EQ, GT egyike) két paraméteres függvény kell. Ez a függvény is lehet a quicksort paramétere:
Ezután már általánosabban lehet használni a quicksort függvény:
Talán a legjellegzetesebb példa magasabbrendű függvények használatára a map, mely egy függvényt és egy listát vár, és eredménye egy lista, melyben a függvény eredményei állnak a bemenő lista elemeire: