A lista egy rendkívül hasznos adatstruktúra. Csakúgy mint a legtöbb funkcionális programozási nyelvben a listának kitüntetett a szerepe az ML -ben. A lista az ML -ben olyan mint egy láncolt lista a C -ben vagy a PASCAL -ban, de a mutatók bonyolult kezelése nélkül. Egy lista ugyanolyan típusú elemek sorozata. Kétfajta lista konstruktor létezik: az üres lista (nil és a cons operátor "::". A nil konstruktorral üres listákat hozhatunk létre. A "::" operátor bal oldalán egy elem szerepel, jobb oldalán egy lista, amiből az eredeti listánál egyel hosszabb listát hoz létre. Példák:
nil | [ ] |
1::nil | [ 1 ] |
2::(1::nil) | [ 2,1 ] |
3::(2::(1::nil)) | [ 3,2,1 ] |
Valójában a cons operátor jobbra asszociatív, így a zárójelek nem szükségesek. Egyszerűen írhatunk 3::2::1::nil -t a [ 3, 2, 1 ] lista létrehozásához. Megfigyelhetjük, hogy a "::" mindig egy elem és egy lista között áll. Az "::" operátort használhatjuk arra, hogy egy elemet adjunk egy lista fejéhez. A "@" operátort használhatjuk listák összefűzésére. Gyakran elkövetett hiba, hogy összekeverik az egy elemet tartalmazó listát egy elemmel. Például a 4 -et hozzávehetjük a [ 5, 6, 7 ] lista elejére a "::" operátorral: 4 :: [ 5, 6, 7 ] vagy írhatjuk azt, hogy [ 4 ] @ [ 5, 6, 7 ]. A 4 @ [ 5, 6, 7 ] és a [ 4 ] :: [ 5, 6, 7 ] hibát okoz. A 4 -et a lista végére szeretnénk rakni, nem írhatjuk azt, hogy [ 5, 6, 7 ] :: 4 csak azt, hogy [ 5, 6, 7 ] @ [ 4 ]. (Aminek az eredménye [ 5, 6, 7, 4 ].)
Amikor listákon értelmezet függvényeket definiálunk általában a következő két mintát használjuk:
Azonban nem szükségszerűen mindig ez az eset. Vegyük például a last függvényt, amely egy lista utolsó elemét adja vissza:
A "szokásos" két minta nem jó erre az esetre. Vegyük például az üres lista utolsó elemét. Mi egy üres lista utolsó eleme?? Ez egy olyan egyszerű kérdés, mint a "mi egy üres lista szorzata?". A "last nil" kifejezésnek nincs értelmes értéke, ezért legjobb, ha nem definiáltnak hagyjuk. Tehát ahelyett, hogy a szokásos módon egy nulla hosszú listából indulnánk ki, most egy 1 hosszú listából fogunk kiindulni. A [h] minta illeszkedik minden olyan listára, amely pontosan egy elemet tartalmaz.
Ennek a függvénynek két speciális tulajdonsága is van, a nem-teljesség és a minták átfedése.
Készítsünk egy függvényt, amely egy lista összes elemét összeadja:
Két alapvető minta létezik listákhoz - mivel kétfajta listakonstruktor van (:: és a nil). A "::" szimbólumot cons -nak hívják, és két komponense van, a nil egy üres listát csinál. Mindkét fajta konstruktorhoz írhatunk egyenleteket (mintaillesztéshez). Az üres listára könnyű elkészíteni az összegző függvényünket, mivel üres lista összege definíció szerint nulla:
A cons konstruktor esetében a lista fejelemét (h, mint head) hozzá kell adnunk a maradék lista (t, mint tail) összegéhez. Ez egy rekurzív függvény lesz. A rekurzió terminálása biztosított, mivel egyre kisebb méretű listákra hívjuk meg a sum függvényt, és üres listákra már definiáltuk a függvényt. A teljes sum függvény:
A infix @ egy előre definiált függvény, de mi is levezethetnénk a definícióját a következőképpen: az összefűzés (@) operátor két listát kapcsol össze, például:
Mivel két paraméterünk van, és mindkettő lista, választhatunk, hogy melyik szerinti rekurziót alkalmazunk. Ha a második paraméter szerinti rekurziót alkalmazunk, a függvény (a jobb oldalak nélkül) így fog kinézni:
Ez azonban nem bizonyul hasznos definíciónak, így az első paraméter szerinti rekurziót fogunk alkalmazni:
Az első egyenlet könnyű, mivel, ha az üres listát fűzzük össze egy másik listával, az eredmény egyszerűen a másik lista lesz. A második egyenlet egy kicsit komplikáltabb. Az első listát (h::t) a második (x) lista elé kell helyeznünk. Ezt úgy kaphatjuk meg, ha h -t cons -oljuk (a cons vagy :: operátorral egy elemet tudunk egy lista elejére rakni), a maradék (t) lista és x összefűzésével kapott listához. A @ definiálásához felhasználtuk a @ definícióját is, tehát ez is egy rekurzív definíció:
Megjegyzés: Természetesen a @ operátor előre definiált (predefined) operátor. A @ operátor tényleges definíciója egy kicsit bonyolultabb annál, mint amit itt levezettünk.
Nézzünk egy függvényt, amely a paraméterül kapott lista összes elemét megduplázza:
Ismét két esetet fogunk kezelni: a nil -t és a (h::t) -t. Az alapeset a nil:
A leggyakoribb hiba azt hinni, hogy doublist nil az 0. Ha csak a típusokra figyelünk, akkor is látjuk, hogy ez értelmetlen lenne, mivel a doublist kimenetének listának kell lennie, és nem egy egész számnak. A cons (::) esetében egyszerűen a fejelem (h) dupláját hozzáfűzzük a maradék lista (t) doublist -jéhez.
Megfigyelhetjük, hogy sok rekurzív függvényben ugyanazok a minták szerepelnek. A következő függvények megdupláznak ill. megnövelnek minden elemet egy listában:
Tipikus végrehajtások:
Természetesen alkalmazhatunk absztrakciót, és írhatunk egy függvényt, amely egy tetszőleges függvényt alkalmaz egy lista elemeire, ez a map:
A map segítségével alternatív definíciót adhatunk a doublist és az inclist függvényekre:
A reduce függvénynek három bemenete van: egy bináris (két argumentumú) függvény, egy kezdőérték és egy lista. A megadott listára ismétlődve alkalmazza a függvény a kezdőértéktől kezdve. Például:
Figyeljük meg a kapcsolatot a sum és a flatten függvényekkel. (A flatten függvény listák listájából csinál egy sima listát).
Tipikus végrehajtások:
Ezeket megvalósíthatjuk a reduce minta segítségével is - van egy kezdőértékünk az üres listára, a cons -ra pedig alkalmazzuk a bináris függvényünket a fejelemre és a rekurzív hívás eredményére:
Ezután újradefiniálhatjuk a sum és a flatten függvényeket:
Valójában még ennél is jobbat tehetünk: az ML megengedi, hogy átalakítsuk az infix függvényeket, mint a milyen a + vagy a @ prefix függvényekké az op kulcsszó segítségével:
Az előre definiált fold függvény ugyanaz mint a reduce csak az argumentumok más sorrendben szerepelnek. A reduce–t így is definiálhatjuk.
A zip -et arra használhatjuk, hogy egy két változós (bináris) függvényt alkalmazunk két listára, például:
A zip a következőképpen van definiálva:
A filter egy predikátumból (olyan függvényből ami igazat vagy hamisat ad vissza) és egy listából egy olyan listát állít elő, amelyben a lista csak azon elemei szerepelnek, amelyre a predikátum igaz. Például nézzük a páros (even) függvényt:
most alkalmazzuk a filter függvényt az [1, 2, 3, 4, 5, 6] listára.