F#-ban a típusokat a következő szintaxisnak megfelelően írhatjuk:
type :=
Az F# a következő elemi típusokat támogatja:
byte, sbyte, int16, uint16, int32, uint32, int64, uint64, obj, float32, float64
Az F#-ban használható változók típusai mind fordítási időben eldőlnek. Az automatikus típuskikövetkeztetésnek köszönhetően a kódban nagyon kevés az explicit típusmegadás. Az automatikus típuskikövetkeztetés azt jelenti, hogy a fordító ki tudja következtetni a változó típusát a kódból. (Bővebben lásd: Típusos lamba kalkulus)
Az alapvető adattípusok az F# esetében eltérnek a többi nyelvben megszokottól, a következőkben ezeket fogjuk tárgyalni.
A rendezett n-es egy olyan egyszerű adattípus, melybe kettő vagy több értéket lehet csoportosítani úgy, hogy ezek az értékek akár különböző típusúak is lehetnek, például int és string.
Az első példa bemutatja, hogy hogyan lehet létrehozni és megsemmisíteni egy ilyen típust:
Amint az látható, a fordító meg tudta határozni az értékadás jobb oldalán álló kifejezés típusát, és az FSI ki is írta a standard outputra, hogy ellenőrizni tudjuk. Természetesen kettőnél több elemet is berakhatunk egy tuple-ba, de arra oda kell figyelni, hogy a tuple típusa a tartalmazott elemek számától függően változik, így alkalmatlan arra, hogy előre nem definiált számú elemet tároljunk benne. Ere a célra a lista vagy a tömb típus a megfelelő választás.
A sor elemeinek a num és str változókba történő kinyerése mintaillesztéses szintaxissal történik, melyet gyakran alkalmazunk F#-ban. A mintaillesztésről még lesz szó a későbbiekben.
A tuple típus kiválóan alkalmas olyan függvények visszatérési értékeként, mely függvényeknek több kimenő adata is lenne. Ugyanis szügségtelenné teszi új osztályok vagy referenciák deklarálását, ha valamilyen egyszerű függvény éppen több visszatérési értékkel rendelkezne. Általában az a módszertan terjedt el, hogy akkor használjuk függvény visszatérési értékének ezt a típust, ha a függvény egyszerű (például maradékos osztás) vagy lokális (azaz más modulok nem használhatják) vagy ha mintaillesztéssel akarjuk használni. Arra az esetre, ha valamilyen bonyolultabb struktúrájú visszatérési értéke lenne egy függvénynek, akkor erre a célra a rekord típust használhatjuk.
A rekordot tulajdonképpen egy olyan rendezett n-esnek tekinthetjük, mely névvel ellátott tagokkal rendelkezik. Ezeket címkéknek nevezzük, melyeket a pont operátor segítségével érhetünk el. Akkor ajánlott ennek az adattípusnak a használata, ha nehéz eldönteni, hogy egy rendezett n-es alkotóelemei mit reprezentálnak. Egy másik különbség a rekord és a tuple típus között, hogy a rekordot előre deklarálni kell a type konstrukció segítségével.
Az utolsó parancsban egy érdekes konstrukciót használunk, ez a with kulcsszó. Alapértelmezés szerint a rekord típus immutable, azaz a tagjai nem módosíthatók. Mivel adattagjai nem módosíthatók, ezért gyakran kell úgy másolatot készíteni egy rekordról, hogy egy (vagy több) adattagján változtatást hajtunk végre. Mivel nem lenne praktikus, ha mindig fel kellene sorolni az adott rekord minden adattagját, ezért az F# támogatja a with kulcsszó használatát.
Az F# rekordjai nagyon hasonlítanak az osztályokra, sőt úgy is tekinthetünk rájuk, mint leegyszerűsített osztályokra. Mivel a rekord immutable, az F# strukturális összehasonlítást végez, mikor össze kell hasonlítani két rekordot. (Ahelyett, hogy az alapértelmezett referencia szerinti összehasonlítást végezné el, amit az osztályoknál szokott alkalmazni.)
Ezt a típust olyan adattípusok reprezentálására használják, melyekben a tárolt adtok opcionálisak. (Természetesen a lehetőségeket már akkor ismerjük, mikor írjuk a kódot.) Egy általános példa erre az adattípusra az absztrakt szintaxisfa:
Ahhoz, hogy használni is tudjunk egy ilyen típust, a mintaillesztés módszeréhez kell nyúljunk. Ebben az esetben a match nyelvi konstrukciót használjuk, mely alkalmas arra, hogy megvizsgáljon egy értéket, hogy a lehetséges minták közül melyiknek felel meg. Az Expr típus esetében a szóba jövő lehetőségek a deklarációkor megadott azonosítók alapján adhatók meg (név szerint): Binary, Variable és Constatnt. Az alábbi példában deklarálunk egy eval nevű függvényt, mely kiértékeli a paraméterként kapott kifejezést. (A használt getVariableValue egy olyan függvény, melynek visszatérési értéke a paraméterként kapott változó értéke.)
Egy függvény deklarációjakor a let kulcsszót arra használjuk, hogy az értéket a névhez kössük. A rekurzív függvényeket explicit jeleznünk kell a nyelvben, ezt a rec kulcsszóval tehetjük meg.
A diszkriminánsos unió tökéletes komplemense a tipikus objektumorientált öröklődési struktúrának. Az OO hierarchiában az ősosztály deklarálja az összes metódust, melyeket a származtatott osztályok felüldefiniálhatnak, tehát nagyon egyszerű egy új típust adni egy értéknek, egyszerűen csak származtatunk egy új osztályt. Viszont egy művelet hozzáadása azt jelenti, hogy az összes származtatott osztályon módosításokat kell végrehajtani. A diszkriminánsos unió esetében előre definiálja az összes típust, ami azt jelenti, hogy egy új művelet hozzáadása nagyon egyszerű, de egy új típus hozzáadása esetén az összes megírt művelet módosítása szükséges.
Az értékek gyűjteményét tároló adatstruktúrák a lista és a tömb. Az F# listája a szokásos láncolt lista, melyet több funkcionális programozási nyelv is támogat.
Lehet egy lista üres, amit [] -val jelölünk vagy egyetlen cella, mely egy értéket és egy hivatkozást tartalmaz a lista következő elemére ( value::tail ).
Lehetőségünk van egy listát egyszerűsített szintaxissal megadni, ami annyit jelent, hogy egyszerűen felsoroljuk az elemeket ( [1; 2; 3;] ) ahelyett, hogy így írnánk 1::2::3::[] .
A tömb egy .NET kompatibilis mutable tömb típus, melyet folytonos memóroiaterületen tárolunk, s ezért nagyon hatékony. Mivel mutable, a tömböt gyakran imperatív programozási stílusban is használják.
A következő példában egy lista deklarációját láthatjuk, és egy olyan rekurzív függvény implementációját, amely összeadja a lista elemeit.
Itt jegyezzük meg, hogy a lista egy generikus típus, tehát bármilyen F#-ban használható típust tudunk benne tárolni. A fenti példában a listánk list<int> típusú, tehát a listánk integereket tárolhat. A generikus típusokat használó függvényeket meg tudjuk szorítani bizonyos típusokra. Illetve maga a függvény is lehet generikus, azaz működthet bármilyen típusú listára. Például egy olyan függvény, mely egy lista utolsó elemét adja vissza, minden gond nélkül lehet generikus, hiszen az utolsó elem nem függ a típusától. Egy ilyen függvény szignatúrája a következő lenne:
Az F# a többi funkcionális nyelvhez hasonlóan, a függvényeket úgy kezeli, mint bármilyen más típust. Paraméterként lehet őket adni más függvélnyeknek, illetve visszatérési érték is lehet. Ezen felül generic típus is lehet függvény. (Tehát készíthető olyan lista, melynek elemei függvények.) Azokat a függvényeket, melyeknek paraméterük vagy visszatérési értékük is függvény, magasabb rendű függvényeknek nevezzük (higher-order function). A következő példában egy olyan függvélnyt definiálunk, ami egy olyan függvénnyel tér vissza, mely egy egész számhoz hozzáad egy számot.
A createAdder függvény törzsében a fun kulcsszót arra használjuk, hogy létrehozzunk egy új, névtelen függvényt. A createAdder típusa (int -> int -> int) azt jelenti, hogy amikor a függvényt meghívjuk egy int paraméterrel, akkor az egy olyan függvénnyel tér vissza, ami egy int-et vár paraméterként és a visszatérési értéke int lesz. A fenti példát egyszerűsíthetjük, mert minden több paramétert váró függvényt úgy foghatunk fel, mint egy olyan függvényt, amely egy függvénnyel tér vissza az első paraméter átadása után. Tehát az alábbi függvény ugyanazt csinálja, mint az előző példában megadott:
Amikor az add10 függvényt deklaráltuk, egy olyan függvényt használtunk, ami két paramétert vár, de mi csak egyet adtunk meg. Az eredmény egy olyan függvény, aminek az első paramétere fix, és már csak egy paramétert vár, a másodikat. A függvények ezen használatánk módját currying-nek nevezik.
A következő példában bemutatjuk a pipeline (|>) operátort, és azt is láthatjuk, hogy a currying hogyan teszi egyszerűbbé, olvashatóbbá a kódot. Továbbá használni fogjuk az előbb megírt add függvényt is.Érdemes megjegyezni, hogy a listák manipulálására használt függvények mind generic-ek.
A funkcionális programozásban lehetőség van a függvénykompozíció operátor használatára. Ez azt jelenti, hogy nagyon egyszerűen lehet olyan függvényt írni, ami a kapott paraméterrel meghív egy függvényt, majd ennek a függvénynek a visszatérési értékével meghív egy másik függvényt. Például komponálhatunk egy olyan fst függvényt, ami egy rendezett párt kap paraméterként, és annak első elemét adja vissza nagybetűssé konvertálva:
Az első utasítás csak komponálja a függvényeket és meghívja a visszaadott függvényt egy rendezett párral, mint paraméterrel. A harmadik utasításban válik nyilvánvalóvá a módszer valódi előnye, ahol már a függvénykompozíciós operátort (>>) arra használjuk, hogy egy olyan függvényt építsünk fel, melyet a map függvénynek adunk paraméterként. A függvénykompozíció révén lehetőségünk van olyan függvények készítésére, melyek nem használnak explicit lambda függvényt (olyat, amihez a fun kulcsszót használjuk), melynek hatására a kód sokkal tömörebb és olvashatóbb lehet.
Az option típust akkor használjuk az F#-ban, ha egy rögzített névhez lehetséges, hogy nem tartozik tényleges érték. Az option típus hordozhat egy adott típusú értéket, vagy lehet üres.
A None érték akkor használatos, ha az option típusnak nincs értéke. Egyéb esetben a Some( ... ) kifejezés adja meg az option értékét.
A Some és None értékek hasznosak a mintaillesztésben is, ahogy az alábbi exists függvényben, aminek visszatérési értéke true
ha az option típusnak van értéke és false ha nincs.
Az option típust gyakran használjuk, ha egy keresés esetleg nem ad vissza találatot.
Az F# 3.0-ban bevezetésre került egy új funkció, amelyet típusszolgáltatónak nevezünk. Egy típusszoltáltatót akkor használunk, ha nem a programban tárolt adatokkal, hanem például adatbázissal, webes adatokkal, vagy strukturált szöveges adattal (Pl.: JSON, XML) szeretnénk dolgozni. Az egyes típusszolgáltatók segítségével nem kell manuálisan megírnunk az adathozzáférési réteget, az F# ezt automatikusan legenerálja a számunkra. Számos beépített típusszolgáltató közül választhatunk és saját magunk is készíthetünk típusszolgáltatókat.
Az alábbi négy típusszoltáltatót adatbázisokhoz és webservice-ekhez való kapcsolódásra használhatjuk:
A LINQ-to-SQL típusszolgáltató segítségével egy SQL adatbázishoz generálhatunk le típusokat, ha rendelkezünk élő adatbáziskapcsolattal. Ehhez a megfelelő assemblyk és importok meglétén kívül két kritikus sorra van szükség a kódunkban ahhoz, hogy lekérdezhessünk egy SQL adatbázist típusszoltáltató segítségével. Először példányosítanunk kell a típusszolgáltatót. Ehhez az SqlDataConnection egy speciális típusát kell létrehoznunk statikus generikus paraméterekkel. Az SqlDataConnection egy típusszolgáltató, és a Connection Stringet felhasználva hozhatjuk létre a számunkra szükséges speciális típust:
Jelen példában a szerverünk a MYSERVER, az adatbázispéldányunk az INSTANCE, az adatbázisunk neve MyDatabase, és az adathozzáféréshez Windows Authentikációt használunk. A dbSchema tartalmazza az összes generált típust, amelyek reprezentálják az adatbázis táblákat, és a db objektum publikus tagjaival férhetünk hozzá ezekhez a táblákhoz. A táblanevek propertyk, és a táblák típusát az F# fordító generálja. Ezek a típusok a dbSchema.ServiceTypes beágyazott típusaiként jelennek meg. Így minden adat, amelyet a táblák soraiból nyerünk, a táblához generált típusok valamelyikének egy példánya.
Ha a table1-et megvizsgáljuk, láthatjuk, hogy a típusa System.Data.Linq.Table<dbSchema.ServiceTypes.Table1>, és a generikus argumentum jelzi, hogy a tábla minden egyes sorának a generált Schema.ServiceTypes.Table1 típus feleltetődik meg. A fordító az adatbázis összes táblájához hasonló típusokat készít.
Lekérdezéseke a query kulcsszó segítségével fogalmazhatunk meg.
A query kulcsszó jelzi, hogy a kifejezés egy lekérdezés. Ekkor a lekérdezés eredménye a generált típusok egy gyűjteménye, a query1 objektum az IQueryable<T> interfész leszármazottja. A lekérdezés lusta kiértékelésű, tehát az adatbázislekérdezés csak akkor hajtódik végre, amikor a lekérdezés kiértékelődik. A lekérdezések felsorolhatóak, és szekvenciákként iterálhatóak.
Egy sor beszúrása az InsertOnSubmit metódussal lehetséges, ha több sort szeretnénk beszúrni, akkor az InsertAllOnSubmit metódust használhatjuk. Miután meghívtuk a metódusokat, az adatbázis tartalma még nem változik. Ehhez szükséges a SubmitChanges metódus meghívása, ekkor történik tényleges commit az adatbázisba. Alapértelmezetten minden ami a SubmitChanges előtt változott, ugyanannak a tranzakciónak a része lesz a metódus lefutásakor.
A törléshez is rendelkezésre állnak a DeleteOnSubmit, DeleteAllOnSubmit metódusok, használatuk hasonló a beszúráshoz.
Egy webszerviz szolgáltatást is hasonlóan tudunk elérni, mint egy SQL szervert, ehhez szükséges egy WsdlService speciális típus létrehozása, melyben generikus paraméterként megadhatjuk a webszerviz elérési útját. A WSDL kapcsolat konfigurációs beállításait a projekt app.config fájljában, vagy statikus paraméterként a típusszolgáltató deklarációjában adhatjuk meg.
Ha létrehoztuk a specifikus típusszolgáltatónkat, nincs más dolgunk, mint meghívni a webszervizt a típusszolgáltatón keresztül, és feldolgozni az eredményt
A beépített típusszolgáltatók (pl.: OData, WSDL) a LINQ-to-SQL-hez hasonló módon használatosak, először létre kell hoznunk egy kapcsolatot a külső adatszolgáltatóval, ebből lekérdezni a DataContext-et, ez legenerálja a reprezentációhoz szükséges típusokat, ezután ezeken a típusokon keresztül manipulálhatjuk a külső adatokat.
Ha a beépített típusszolgáltatók nem felelnének meg az igényeknek, az F# lehetőséget biztosít saját típusszolgáltatók létrehozására, és használatára. Erre szükség lehet ha például egy olyan adathalmazhoz szeretnénk elérési réteget biztosítani, amely folyamatosan növekszik, és mindegyik adatnak ismerjük a sémáját. Ekkor írhatunk egy olyan típusszolgáltatót, amely beolvassa a sémákat, és az adathalmaz jelenlegi formáját szigorú típusosság kiegészítésével tárja a programozó elé.
A "code quotations" egy olyan nyelvi elem, ami lehetővé teszi, hogy F# kód kifejezéseket generáljunk és dolgozzunk velük. Ezzel egy absztrakt szintaxis fát generál a nyelv, ami reprezentálja a kódot. Az absztrakt szintaxis fa ezek után bejárható és feldolgozható a programunk igényeinek megfelelően. Például arra használható ez a fa, hogy F# kódot vagy más programozási nyelv kódját generáljuk ki.
Egy idézett kifejezés egy olyan módon megjelölt része az F# kódunknak, ami nem a program részeként kerül fordításra, hanem egy olyan objektumba kerül fordításra, ami reprezentálja az F# kódunkat. Kétféleképpen lehet megjelölni az idézett kifejezéseket: vagy típus információval vagy típus információ nélküli jelöléssel. Ha típus információval is el akarjuk látni, akkor a <@ és @> elválasztó jeleket használjuk az idézett kifejezésben. Ha nincs szükség típus információ mentésére, akkor a <@@ és @@> jelöléseket használhatjuk. A következő kódrészlet megmutatja mindkettő használatát:
Egy nagy kifejezésfa bejárása gyorsabb, ha nem rendelünk mellé típus információt. Egy idézett kifejezés típusa - ha a típus szimbólummal együtt idézzük - Expr<'T> lesz, ahol a típus paraméter az lesz, ami a kifejezés típusa is. Ezt az F# fordítójának típus következtető algoritmusa állapítja meg. Amikor kód idézést használunk típus információ nélkül, akkor az idézett kifejezés típusa egy nem-generikus típus, az Expr lesz. Ha egy típusos idézett kifejezésnek akarjuk a típus megfejlölés nélküli változtatát elérni, akkor használjuk rajta a Raw tulajdonságot, ami pontosan ezt adja vissza.
Az F# rengeteg lehetőséget ad arra, hogy programkód szinten generáljunk F# kifejezés objektumokat az Expr osztály használatával. Így elkerülhetjük az idézett kifejezések használatát.
Fontos észrevétel, hogy a kód idézeteknek egy teljes kifejezést kell tartalmaznia. Egy let kötéshez például, szükség van a kötés nevéhez tartozó definícióra, valamint egy ráadás kifejezés megadására, ami használja ezt a kötést. Ez tehát egy olyan kifejezés lesz, amit követ egy in kulcsszó. Egy modul felső szintjén ez csak a következő kifejezése lesz a modulnak, de idézetben már explicit szükséges.
Ezért a következő kifejezés nem érvényes.
Viszont a következő kifejezések igen.
Hogy idézett kódot használhassunk, adjuk hozzá a következő névteret az import deklarációhoz (az open kulcsszó használatával): Microsoft.FSharp.Quotations
Az F# PowerPack támogatást nyújt az F# kifejezés objektumok kiértékeléséhez és futtatásához
Az Expr típusnak egy példánya egy F# kifejezést reprezentál. Mind a generikus és nem-generikus Expr típusok dokumentálva vannak az F# könyvtár dokumentációjában. Több információért keress rá a Microsoft.FSharp.Quotations Namespace (F#), valamint a Quotations.Expr Class (F#) könyvtár leírásokra.
Az összevonás segítségével lehetőség van tényleges kód idézeteket összevonni olyan kifejezésekkel, amiket vagy mi írtunk programkód szinten vagy egy másik kód idézetből származik. A % és %% operátorok megengedik, hogy F# kifejezés objektumot kód idézethez adjuk hozzá. Ha típusos kifezés objektumot akarunk egy típusos idézethez adni, akkor a %-ot, ha nem típusos kifejezés objektumot akarunk egy nem típusos idézetbe szúrni, akkor pedig a %% operátort használjuk. Mindkettő operátor unáris, prefix operátor. Így ha expr egy nem típusos kifejezés az Expr típusnak, akkor a következő kódrészlet érvényes.
Valamit, ha expr egy típusos kifejezése az Expr