F# programozási nyelv Legfrissebb változat: 2.0 (.NET 4.0 szükséges hozzá) Hivatalos honlap: http://research.microsoft.com/en- us/um/cambridge/projects/fsharp/ Kiss Tamás 2010.05.13
F# nyelv főbb tulajdonságai: OCaml nyelvet vették alapul a tervezéshez, de több nyelvi elemet a Haskelltől is átvettek. A .Net keretrendszer teljesen elérhető f# alól. Multiparadigma nyelv. Megtalálható benne a funkcionális, objektum orientált és a generikus programozás elemi is. A kifejezések típusának meghatározásához típus kikövetkeztetést használ. Megfelel a CLI-nek(Common Language Infrastructure). IL kódra fordítható és van interpretere is. OCaml kód szabványos f# kód is. A fordító egyrésze a mai napig OCamlben íródott. A kód fentről lefelé értékelődik ki és a kifejezések felüldefiniálhatóak
F# nyelv története: A nyelvet 2002-ben kezdték el fejleszteni Don Syme vezetésével. A fejlesztés a Microsoft Resarch cambridgei részlegénél történt. Első hivatalos változat 2005. Azóta 3-4 havonta új változat kerül ki. Az utolsó nagy mérföldkő pedig a 2.0 kiadása amely néhány hónapja került kiadásra. Ez már a Visual Studio 2010 beépített része. Cél: Egy olyan népszerű nyelv kialakítása mely ötvözi a funkcionális és az OO elmeket és mindezeket hozzáilleszti a .NET keretrendszerhez.
Lexikális elemek: Azonosítók: unb::=Unicode betű Azonosítók::=(unb | _ | ')(unb | _ | ' | szám) Case-sensitive a nyelv Kommentek: Egysoros komment // Több soros komment (* *) Dokumentáló kommentek /// (Megegyezik a C#-al)
Literálok: "alma", 76, 56l A számok után megadható típus módosító pl(y,uy,s...) Preprocesszor direktívák: #light #light bekapcsolja az F# egyszerűsített szintaktisát. Ekkor a funkcionális programozásban megszokott kódok írhatóak. Ilyenkor a számít a behúzás és a sor vége karakter is. A blokk elejét és a végét a behúzás határozza meg. Enélkül az F# nem érzékeny a szóközökre.
Operátorok: Elemi operátorok: = <> <= >= ||, &&, +, -, *, /, % (modulo) , ** (hatványozás) Bitenkénti operátorok: &&& bitenkénti és ||| bitenkénti vagy ^^^ bitenkénti kizáró vagy <<< balra csúsztatás >>> jobbra csúsztatás
További operátorok: |> pipe >> függvény kompozíció :: lista konstrukor @ string konkatenálás :? dinamikus típus ellenőrzésre szolgál :> típus konverzió lefelé :?> típus konverzió felfelé (futási idejű kivételt válthat ki)
További lexikális elmek: fun - lambda függvény definiálása
Let kifejezés let fvnev (pattern) = exp Az f# nyelvben általában nem beszélhetünk értékadásról(funkcionális). Értékadásnak felfogható a mutable változókon végzett művelet. Legfontosabb kifejezés: let fvnev (pattern) = exp Alapesetben ezek nem változók csak szinonimák a megfelelő kifejezésre. Sajnos F#-ban felüldefiniálhatók így a hivatkozási hely függetlenség nem teljesül :( Engedélyezett a let kifejezések egymásba ágyazása. Ekkor a változó láthatósági köre az őt definiáló kifejezésen belül van.
Példák I. #light let alma=5 let double x = 2*x let mutable aktualisCim="Budapest, Egyetem tér 1-3" printfn "aktualisCim változó tartalma %A" (aktualisCim) printfn "Alma változó tartalma %A" (alma) printfn "double 5: %d" (double 5) aktualisCim <- "Budapest, Pázmány Péter sétány 1" printfn "aktualisCim változó új tartalma %A" (aktualisCim) System.Console.ReadKey () |> ignore
Típusok: Az elemi típusok megegyeznek a C# típusaival: sbyte,byte,int16,uint16... Viszont a bitenkénti operátorok különböznek!!!: &&& , |||, ^^^, ~~~ Opcionális értékű típus Option Két értéke lehet: None vagy Some típus Unit típus, egy értéket tartalmaz a ()-t. Ez megegyezik a Clean és a Haskell unit típusával. Azt jelzi hogy csak számítást végez a függvény nem ad vissza értéket. mutable típusok. Létrehozáskor meg kell jelölni őket a mutable kulcsszóval. A ‘<-’ operátorral adható neki új érték. Megfeleltethető az imperatív programozás értékadásával.
Függvény típusok Függvény típus: type1 -> type2 ez az első típusról képez a második típusra. Anonim(lambda) függvények: fun x -> x*x Mivel alaptípusok széles körben használhatóak Infix függvény is definiálható Pl: (>|) x f = f x Rekurzív függvények Definiálása: let rec fvnev=...
Összetett típusok Rendezett n-es: Tömbök Lista Sorozat Generikus típus Rekord Interfész
Rendezett n-es: Tuple: type_1 * type_2 * ... *type_n Egy adott típusokból álló n-est határoz meg. A különböző tuple-ok különböző típusokat alkotnak Nem lehet indexelni őket. Az elemek elérése minta illesztéssel történik. Osztályok és struktúrák helyett a funkcionális kódokban ezt használják az információk összecsomagolására. Pl: > let tuple = (1, false, "text"); val tuple : int * bool * string
Tömbök type[| |]: Fix méretű változtatható értékű adatszerkezet. Mohó kiértékelésű!!! Pl: > let arr = [|1.0;1.1;2.0|] val arr : float[] > arr[1];; val it : float =1.0
Lista Egy adott típusú elemeket tartalmazó láncolt adatszerkezet. Az f#-ban a lista szigorú kiértékelésű :( Listák megadásai: [] Üres lista expr::expr [expr1;expr2;expr3] Elemek felsorolásával [expr..expr] Intervallum megadásával [for x in list -> expr] generálással (Vigyázat a mohó kiértékelés miatt itt nem lehet végtelen listákat generálni) (Lusta kiértékelésű lista található a f# collectionok között viszont a működése nem tökéletes, ezért használata nem javasolt )
Lista használata Használatuk megegyezik a funkcionális nyelvekkel. Kiértékelése miatt ritkán használják helyette seq (esetleg LazyList) használata ajánlott. Két listát a @ operátorral lehet össze fűzni A listának két konstruktora van [] üres lista 2::lista pedig a lista elejére beszúrja az elemet A lista elemeire minta illesztve vagy a megfelelő függvénnyel lehet hivatkozni pl x::xs, [], [a] head, tail A listához tartozó függvények a List module-ban vannak definiállva. Pl: open List List.filter (fun x -> x%2=0) [1..9]
Sorozatok: seq<type> Type típusú elemek sorozata. Iterálható. Kiértékelése lusta ezért használható végtelen elemű lista helyett. A leggyorsabb adatszerkezet. Amit lehet ezzel érdemes megvalósítani ha fontos a teljesítmény. Pl: > let squares seq { for i in 0..10 -> (i,i*i)} val it : seq <int * int> = [ (0,0) ; (1,1) ; (2,4) ; (3,9) …] let fileinfo dir= seq { for file in Directory.GetFiles(dir) do let creationTime = File.GetCreationTime(file) let lastAccessTime = File.GetLastAccessTime(file) yield (file, creationTime, lastAccessTime) }
Algebrai adatszerkezetek, rekordok Ezzel a szerkezettel megvalósítható a diszkrimináns únió. Pl: type Tree<'a>= | Node of Tree<'a> * Tree<'a> | Leaf of 'a Rekord Pl: type Point3D = { X: float; Y: float; Z:float } let p1 = { X = 3.0; Y = 4.0; Z = 5.0 }
Generikus típus A legtöbb adatszerkezet ellátható típus paraméterrel. Speciális szintaxisa van a típus változóknak ‘a , ‘b …. Sokszor a típus kikövetkeztetőnek segítenünk kell a generikus függvények írásakor.(Explicit típus megadása : -al) Pl: let rec map (f : ‘a -> ‘b) (l : ‘a list ) = match l with | x:: xs -> f x :: map f xs | [] -> []
OO elemek A rekordokhoz megadható adattagok és függvények rajtuk Megadható konstruktor hozzájuk Statikus adattagokkal is rendelkezhetnek Örökölhetnek tulajdonságokat Implementálhatnak interfészeket Rendelkezhetnek változtatható állapottal.
Példa OO type Vector2D(dx: float, dy:float)= let len = sqrt (dx * dx + dy * dy) member x.DX = dx member x.DY = dy member x.Length= len member x.Scale(k) = Vector2D(k*dx , k* dy) static member Zero = Vector2D(0.0,0.0) static member OneX = Vector2D(1.0,0.0) let vect1 = Vector2D(3.0,4.0) printfn "%A„ vect1.Length type UnitVector2D(dx,dy) = let tolerance = 0.000001 do if(abs len - 1.0) >= tolerance then failwith "not a unit vector" new() = UnitVector2D (1.0, 0.0) let unitVector = UnitVector2D(1.001, 0.0)
Példa mutable field-re type MutableVector2D(dx:float,dy:float) = let mutable currDX = dx let mutable currDY = dy member v.DX with get() = currDX and set(k)= currDX <- k member v.DY with get() = currDY and set(k)= currDY <- k let mutVect= MutableVector2D(2.0,3.0) printfn "%A" mutVect.DX mutVect.DX <- 3.0 printfn "Változatás után: %A" mutVect.DX
Interfészek Az F# -ban definiálható interfészek. Itt az interfészt is a type kulcsszóval kell kezdenünk. Minden metódusát abstract jelzővel kell ellátnunk és meg kell adnunk a típusát Pl: type IShape = abstract Contains : Point -> bool abstract BoundingBox : Rectangle Megvalósításkor pedig az összes műveletet meg kell valósítanunk. Erre 2 lehetőségünk van: A new kulcsszóval bevezetve csak az interfészt valósítjuk meg,egy konkrét példánnyal (hasonlít Java InnerClass) Kiterjeszthetjük egyéb műveletekkel is az interfészt:
Példa interfész megvalósítására let circle (center Point, radius:int) = { new IShape with member x.Contains(p:Poinr) = let dx = float32 (p.X - center.X) let dy = float32 (p.y - center.y) sqrt(dx*dx + dy*dy) <= float32 radius member x.BoundingBox = Rectangle(center.X-radius, center.Y-radius,2*radius+1,2*radius+1) } type MutableCircle()= let mutable center = Point(0,0) let mutable radius=10 member sq.Center with get() = center and set(v)= center <-v member sq.Radius with get() = radius and set(v)= radius <-v member c.Perimetre = 2.0 * System.Math.PI* float radius interface IShape with member x.Contains(p: Point)
Az F# kód kiértékelés: Az f# kód az imperatív nyelvekhez hasonlóan sorról sorra értékelődik ki. Nem teljesül rá a hivatkozási hely függetlenség. Ezért nem hivatkozhatunk egy később definiált függvényre ellenben a funkcionális nyelvekben megszokottól. A nevek újra definiálhatóak
Típus kikövetkeztetés F#-ban A fordító vagy az interpreter a kód analizálásával megszorításokat gyűjt amikből a legáltalánosabb típust határozza meg. Az F# a típus kikövetkeztetése egymenetes és nem válik külön a scope analízistől A kikövetkeztetést egy hatalmas ML kód végzi(~több mint 4000 sor).(nagyon lassú...) A kikövetkeztetéshez sokszor segítséget kell adnunk, ha a megfelelő működést szeretnénk. A generikusoknál is ki kell tenni általában a megszorításokat Jelenlegi kutatási terület a kikövetkeztetés 2 részre bontása, ezáltal a fordító kevésbé lenne monolitikus.
Egyéb nyelvi elemek Minta illesztés Ciklusok Let kifejezések egymásba ágyazása Rekurzív let kifejezések Activa pattern .Net és az F#
Minta illesztés match expr with pat ->expr alakban adható meg Ilyenkor az eredmény kifejezéseknek azonos típusúaknak kell lenniük. Több minta is megadható melyeket | jellel kell kezdeni Az illesztett minta elnevezhető as kulcsszóval Illeszthető egy típus konstruktorára is kifejezés. Megadható őrfeltétel a minta után a when kulcsszóval Megadható dinamikus típusvizsgálat is az ágakban
Mintaillesztés példa I. let nagyobb (x:int) y:int = match x with | _ when x<y -> y | _ when x>y -> x | otherwise -> x type Tree<'a>= | Node of Tree<'a> * Tree<'a> * double | Leaf of double member x.Depth = | Leaf(_) -> 0 | Node(ls,rs,_) -> nagyobb ls.Depth rs.Depth + 1 let fa1=Node(Leaf(2.1),Node(Leaf(0.0),Leaf(0.0),0.0),0.0) printfn "Fa mélyége: %d" (fa1.Depth) //Fa mélysége: 2
Mintaillesztés típus vizsgálattal let stringType (t : obj) = match t with | :? System.Boolean -> "Boolean" | :? System.Byte -> "Byte" | :? System.Int32 -> "Int32" | :? System.Double -> "Double" | _ -> "Unknown" printfn "%A" (stringType (box 14uy)) printfn "%A" (stringType (box false)) printfn "%A" (stringType (box "Foo"))
Ciklusok Három féle ciklus van f#-ban. Számlálós ciklus : for var = start-expr to end-expr do expr done Feltételhet kötött ciklus: while expr do expr done Bejáró ciklus : for pattern in expr do expr done #light szintaxis esetén a done-t nem kell kitenni azt az indentálással tudjuk jelölni: Pl: let repeatFetch url n= for i=1 to n do let html = http url printf "fetched <<< %s >>>>\n" html printf "Done!\n"
Let kifejezések egymásba ágyazása Tetszőleges mélységig egymásba ágyazhatóak. Láthatóságuk a megadott blokkon belül él. (Információ rejtés) Ilyenkor a let kifejezés értéke az utolsó kifejezés értéke lesz. Pl: let delimiters = [ ’ ’; ’\n’; ’\t’; ’<’; ’>’; ’=’ ] let getWords s = String.split delimiters s let getStats site = let url = "http://" + site let html = http url let hwords = html |> getWords let hrefs = html |> getWords |> List.filter (fun (site,html.Length, hwords.Length, hrefs.Length)
let generateStamp = let count = ref 0 (fun () -> count := let generateStamp = let count = ref 0 (fun () -> count := !count + 1; !count) let a:int=generateStamp() let b:int=generateStamp() printfn "%d" (a) // 1 printfn "%d" (b) // 2
Rekurzív let kifejezések Jelölnünk kell ha egy let kifejezés rekurzív. Ehhez a let után a „rec” jelzőt ki kell tennünk Vigyáznunk kell arra hogy a definiált függvényünk végrekurzív legyen(tail call). Ha erre nem figyelünk könnyen StackOverFull Exceptiont kaphatunk futás közben. (Ez a leggyakoribb hiba az F# programokban, és utólag nehezen visszakövethető és javítható)
let rec listSum (l:list<int>) = match l with | [] -> 0 | x::xs -> x + listSum xs let lista1=[1;2;3;4;5] printfn "lista1 elmeinek az összege: %d" (listSum lista1) // lista1elmeinek az összege: 15
Active pattern F#-ban lehetőségünk van saját mintaillesztési szabályokat definiálni. Ezeket active pattern-eknek nevezzük. Vegyünk egy példát: Egy komplexszámnál szeretnénk különböző adatokat lekérni 1 lépésben. Ehhez definiáljuk a következő active patternt: let (|Polar|) (x:complex) = (x.Magnitude,x.Phase) Ezek után a szorzás már jóval egyszerűbben, 1 lépésben megadható: let mulViaPolar a b= match a, b with | Polar(m,p), Polar(n,q) -> Complex.mkPolar(m*n,(p+q))
.Net és az F# A nyelv tervezésekor törekedtek arra hogy kompatibils legyen a .Net-el. Az F# alól a .Net elég nagy része elérhető közvetlenül. (Korántsem teljes de folyamatosan bővítik) A .Net típusai teljes mértékben kompatibilisek az F# típusaival. A kommunikáció a .Net-es objektumokkal legtöbbször n-esekkel történik A .Net Collectionok elérhetőek F# alól
Hasznos kiegészítők F# PowerPack http://fsharppowerpack.codeplex.com/ Nunit - Unit tesz f#-hoz is http://www.nunit.org/ FsCheck – Haskell QuickCheck portolása http://fscheck.codeplex.com/ ELTE Fsharp project által fejlesztett Addin http://fsharp.inf.elte.hu/downloads.html#fs harptools
Köszönöm a figyelmet!