Az F# programozási nyelv

Típusok, típuskonstrukciók

Típusszerkezet

F#-ban a típusokat a következő szintaxisnak megfelelően írhatjuk:

type :=
  | type -> type -- függvény típus
  | type * ... * type -- rendezett n-es (tuple) típus
  | ( type ) -- zárójelezett típus
  | ident. ... .ident -- nevesített típus
  | typar -- változó típus
  | type longident -- összetett típus, e.g int list
  | ( types ) longident -- bonyolultabb összetett típus , e.g (int,string) map
  | longident'<'type'>' -- másik szintaxis összetett típusokra, e.g., list<int>
  | type[] -- .NET tömb típus
  | type[,] -- két dimenziós.NET tömb típus
  | type lazy -- lazy típus
  | typar :> type -- megszorításos típus
  | #type -- anonymous megszorításos típus


typar :=
 | _ -- névtelen típusú változó
 | 'id -- változó
 | ^id -- statikus változó

Elemi típusok

Az F# a következő elemi típusokat támogatja:
byte, sbyte, int16, uint16, int32, uint32, int64, uint64, obj, float32, float64

Típuskonstrukciók

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.

Rendezett n-es (Tuple)

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:

> let tuple = (42, "Hello world!");; val tuple : int * string > let (num, str) = tuple;; val num : int val str : string

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.

Rekord típus

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.

> // A rekord típus deklarációja type Product = { Name:string; Price:int };; > // Meghatározzuk a Product értékét type let p = { Name="Test"; Price=42; };; val p : Product > p.Name;; val it : string = "Test" > // Készítünk egy másolatot egy más 'Name' értékkel let p2 = { p with Name="Test2" };; val p2 : Product

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.)

Diszkriminánsos unió típus

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:

> // Az 'Expr' deklarációja type Expr = | Binary of string * Expr * Expr | Variable of string | Constant of int;; (...) > // Hozzunk létre egy olyan 'v' értéket, ami az 'x + 10' -et reprezentálja let v = Binary("+", Variable "x", Constant 10);; val v : Expr

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.)

> let rec eval x = match x with | Binary(op, l, r) -> let (lv, rv) = (eval l, eval r) if (op = "+") then lv + rv elif (op = "-") then lv - rv else failwith "Unknonwn operator!" | Variable(var) -> getVariableValue var | Constant(n) -> n;; val eval : Expr -> int

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.

Lista típus

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.

> let nums = [1; 2; 3; 4; 5];; val nums : list > let rec sum list = match list with | h::tail -> (sum tail) + h | [] -> 0 val sum : list -> int

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:

last: list<'a> -> 'a

Függvény típus

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.

> let createAdder n = (fun arg -> n + arg);; val createAdder : int -> int -> int > let add10 = createAdder 10;; val add10 : int -> int > add10 32;; val it : int = 42

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:

> let add a b = a + b;; val add : int -> int -> int > let add10 = add 10;; val add10 : int -> int

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.
> let nums = [1; 2; 3; 4; 5];; val nums : list > let odds_plus_ten = nums |> List.filter (fun n-> n%2 <> 0) |> List.map (add 10) val odds_plus_ten : list = [11; 13; 15];;

Érdemes megjegyezni, hogy a listák manipulálására használt függvények mind generic-ek.

Függvény kompozíció

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:

> (fst >> String.uppercase) ("Hello world", 123);; val it : string = "HELLO WORLD" > let data = [ ("Jim", 1); ("John", 2); ("Jane", 3) ];; val data : (string * int) list > data |> List.map (fst >> String.uppercase);; val it : string list = ["JIM"; "JOHN"; "JANE"]

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.

Option típus

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.

let exists (x : int option) = match x with | Some(x) -> true | None -> false

Az option típust gyakran használjuk, ha egy keresés esetleg nem ad vissza találatot.

let rec tryFindMatch pred list = match list with | head :: tail -> if pred(head) then Some(head) else tryFindMatch pred tail | [] -> None // result1 visszítérési értéke Some 100 és a típusa int option. let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] // result2 visszítérési értéke None és a típusa int option. let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]

Típusszolgáltatók

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ó használata

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:

type dbSchema = SqlDataConnection<"Data Source=MYSERVER\INSTANCE;Initial Catalog=MyDatabase;Integrated Security=SSPI;"> let db = dbSchema.GetDataContext()

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.

let table1 = db.Table1

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.

Adatok lekérdezése LINQ-to-SQL-ben

Lekérdezéseke a query kulcsszó segítségével fogalmazhatunk meg.

let query1 = query { for row in db.Table1 do select row } query1 |> Seq.iter (fun row -> printfn "%s %d" row.Name row.TestData1)

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.

Adatok frissítése LINQ-to-SQL-ben

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.

let newRecord = new dbSchema.ServiceTypes.Table1(Id = 100, TestData1 = 35, TestData2 = 2.0, Name = "Testing123") // Insert the new data into the database. db.Table1.InsertOnSubmit(newRecord) try db.DataContext.SubmitChanges() printfn "Successfully inserted new rows." with | exn -> printfn "Exception:\n%s" exn.Message

A törléshez is rendelkezésre állnak a DeleteOnSubmit, DeleteAllOnSubmit metódusok, használatuk hasonló a beszúráshoz.

Webszerviz elérése típusszolgáltatón keresztül

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.

type TerraService = WsdlService<"http://msrmaps.com/TerraService2.asmx?WSDL">

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

try let terraClient = TerraService.GetTerraServiceSoap () let myPlace = new TerraService.ServiceTypes.msrmaps.com.Place(City = "Redmond", State = "Washington", Country = "United States") let myLocation = terraClient.ConvertPlaceToLonLatPt(myPlace) printfn "Redmond Latitude: %f Longitude: %f" (myLocation.Lat) (myLocation.Lon) with | exn -> printfn "An exception occurred: %s" exn.Message

Egyéb típusszolgáltatók használata

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.

Saját típusszolgáltató készítése

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é.

Kód "idézetek"

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.

Idézett kifejezések

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:

open Microsoft.FSharp.Quotations // Egy típus információval ellátott idézet. let expr : Expr = <@ 1 + 1 @> // Típus információ nélküli idézet. let expr2 : Expr = <@@ 1 + 1 @@>

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.

// Nem érvényes: // <@ let f x = x + 1 @>

Viszont a következő kifejezések igen.

// Érvényes: <@ let f x = x + 10 in f 20 @> // Érvényes: <@ let f x = x + 10 f 20 @>

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ípus

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.

Összevonás operátor

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.

<@@ 1 + %%expr @@>

Valamit, ha expr egy típusos kifejezése az Expr típusnak, akkor a következő kódrészlet érvényes.

<@ 1 + %expr @>