Készítette: Károlyi László április 4.Programozási nyelvek és paradigmák összehasonlítása 2. GY
Az F# egy multiparadigmális nyelv, amely támogatja a funkcionális, az imperatív, valamint az objektumorientált programozást A.NET keretrendszer elsőszámú funkcionális nyelve Képes együttműködni más.NET-es programokkal A nyelv eredeti tervezője és megalkotója: Don Syme (Microsoft Research, Cambridge) Fejlesztését részben az Ocaml nyelv inspirálta 2Programozási nyelvek és paradigmák összehasonlítása 2. GY
Az F# a Visual Studio beépített nyelve Az FSSF (F# Software Foundation) gondozásában azonban nyílt forráskódú szoftverek is elérhetőek Visual F# Express Tools Xamarin Studio, MonoDevelop (elsősorban mobil alkalmazások fejlesztéséhez) F# compiler tools (önálló fordítóprogram) WebSharper (HTML5 alkalmazások készítéséhez) Try F# (online interaktív környezet) 3Programozási nyelvek és paradigmák összehasonlítása 2. GY
Az F# Interactive (fsi.exe) használható: F# kód interaktív konzolos futtatására F# script-ek végrehajtására REPL (Read-Evaluate-Print-Loop) környezetet ad Ugyanúgy dolgozhatunk benne, mint ha egy forrásfájlt készítenénk Az input végét ;; jelzi Sikeres fordítás után a kód végrehajtásra kerül, továbbá a lefordított típusok és értékek szignatúrája is kiíródik 4Programozási nyelvek és paradigmák összehasonlítása 2. GY
A let kulcsszóval egy nevet kötünk hozzá egy értékhez Ez az érték később már nem módosítható (immutable) 5Programozási nyelvek és paradigmák összehasonlítása 2. GY let duplicated = ”original value” let duplicated = ”new value” // duplicate definition error Van lehetőség módosítható kötések létrehozására is, de ezt explicit módon jelezni kell a mutable kulcsszó használatával A <- operátor felel meg az értékadásnak let mutable modifiable = ”original value” modifiable <- ”new value”
A „könnyűsúlyú” let szintaxis miatt azt hihetnénk, hogy az F# egy dinamikus típusozású nyelv Valójában azonban egy statikus típusrendszerrel rendelkező nyelvről van szó A típuslevezető rendszer fordítási időben kikövetkezteti az értékek típusát Ezáltal a dinamikus típusozású nyelvek olvashatósága a statikus típusrendszerű nyelvek megbízhatóságával párosul 6Programozási nyelvek és paradigmák összehasonlítása 2. GY > let anInt = 10 - let aFloat = let aString = ”I’m a string!”;; val anInt : int = 10 val aFloat : float = 3.14 val aString : string = ”I’m a string!”
Az üzenetek standard output-ra történő kiírása a printfn függvénnyel történhet 7Programozási nyelvek és paradigmák összehasonlítása 2. GY > printfn ”Hello world from F#!”;; Hello world from F#! val it : unit = () Különböző típusú értékek kiírására is van lehetőség formázókarakterek használatával > printfn ”The answer is %d” 42;; The answer is 42 val it : unit = () Az előző kódrészletben a %d jelzi a printfn számára, hogy egy egész típusú értéket kell a kiírandó szövegbe illeszteni
8Programozási nyelvek és paradigmák összehasonlítása 2. GY A fenti kódrészlet rávilágít arra, hogy az F# érzékeny a whitespace karakterekre (whitespace sensitivity) A függvény törzsét az indentálás jelzi A visszatérési érték a függvény utolsó sora A let kulcsszóval egy nevet nem csak egy értékhez, hanem egy függvényhez is köthetünk let add x y = x + y add 2 2
Bizonyos esetekben segíteni kell a típuslevezetést, ehhez nyújtanak segítséget a típusannotációk 9Programozási nyelvek és paradigmák összehasonlítása 2. GY let toHackerTalk phrase = phrase.Replace(’t’, ’7’).Replace(’o’, ’0’) let toHackerTalk (phrase : string) = phrase.Replace(’t’, ’7’).Replace(’o’, ’0’) Az előbbi függvénydefiníció fordítási hibát eredményez A.NET beépített String.Replace függvényét használjuk, de az F# nem tudja, hogy a phrase típusa string A típusannotáció tehát akkor szükséges, amikor a használatból nem következtethető ki az argumentum típusa
Azáltal, hogy a let kulcsszón keresztül nevekhez köthetünk függvényeket, ugyanúgy kezelhetjük őket, mint bármilyen más értéket 10Programozási nyelvek és paradigmák összehasonlítása 2. GY let quadruple x = let double x = x * 2 double (double x) Ugyanez a függvénykompozíció használatával > let quadruple = - let double x = - x * 2 - double >> double;; val quadruple : (int -> int)
A magasabb rendű függvények olyan függvények, amelyeknek valamelyik argumentuma és/vagy visszatérési értéke is függvény 11Programozási nyelvek és paradigmák összehasonlítása 2. GY let twoTest test = test 2 let myTest x = if x = 2 then ”x is 2” else ”x isn’t 2” twoTest myTest
Az F# a fun kulcsszó segítségével lehetőséget biztosít helyben definiált segédfüggvények készítéséhez, amelyeknek nem kell nevet adnunk Az ilyen függvényeket lambda függvényeknek is nevezik 12Programozási nyelvek és paradigmák összehasonlítása 2. GY let add x y = fun x y -> x + y add 2 2 let twoTest test = test 2 twoTest (fun x -> x = 2)
A listák az F#-ban közönséges láncolt listával vannak reprezentálva Létrehozása egyszerű szintaxissal történik 13Programozási nyelvek és paradigmák összehasonlítása 2. GY > let evens = [2; 4; 6; 8];; val evens : int list = [2; 4; 6; 8] Tetszőleges hosszúságú (véges) listák is készíthetők a.. operátorral let firstHundred = [1..100]
A List modul számos listafüggvényt kínál a listákon, illetve annak elemein történő manipulálásra 14Programozási nyelvek és paradigmák összehasonlítása 2. GY let firstHundred = [1..100] let doubled = List.map (fun x -> x * 2) firstHundred Az igazi ereje ennek a modulnak akkor jön elő, amikor ezeket a függvényeket egymással kombináljuk let firstHundred = [1..100] List.map (fun x -> x * 2) (List.filter (fun x -> x % 2 = 0) firstHundred)
Ha túl sok függvényt ágyazunk egymásba, úgy a kód hamar bonyolulttá válhat 15Programozási nyelvek és paradigmák összehasonlítása 2. GY List.sum (List.map (fun x -> x * 2) (List.filter (fun x -> x % 2 = 0) [1..100])) A fenti kód olvasása azért nehézkes, mert az [1..100] listától indulva, bentről kifelé kell haladnunk Ennek elkerüléséhez válik hasznossá a |> operátor [1..100] |> List.filter (fun x -> x % 2 = 0) |> List.map (fun x -> x * 2) |> List.sum
A rekordok definiálása speciális szintaxissal, a type kulcsszó használatával történik 16Programozási nyelvek és paradigmák összehasonlítása 2. GY type Book = { Name : string; AuthorName : string; Rating : int; ISBN : string; } Ezt követően egy Book típusú értéket is használhatunk let kifejezésben let expertFSharp = { Name = ”Expert F#”; AuthorName = ”Don Syme, Adam Granicz, Antonio Cisternino”; Rating = 5; ISBN = ” ”; } Miután létrehoztunk egy könyvet, könnyen hozzá tudunk férni annak mezőihez printfn ”A könyv értékelése: %d/5.” expertFSharp.Rating
Probléma merülhet fel abban az esetben, amikor egy korábban már definiált mezőnevet használunk egy új rekord típusban 17Programozási nyelvek és paradigmák összehasonlítása 2. GY type VHS = { Name : string; AuthorName : string; Rating : string; // A videók más értékelést használnak ISBN : string; } Ezt követően minden olyan rekord példány, amelynek van közös mezőneve a VHS típussal, annak típusa VHS típusúra lesz kikövetkeztetve Ez akkor jelent problémát, amikor egy újabb Book típusú rekordot hozunk létre let theFSharpQuizBook = { Name = ”The F# Quiz Book”; AuthorName = ”William Flash”; Rating = 5; ISBN = ” ”; } A probléma kiküszöbölhető minősítéssel: Book.Rating
Az Option típusok olyan adatokat reprezentálnak, amelyek vagy léteznek vagy nem Ha létezik az adat, akkor az Some típusú Ha nem létezik, akkor a típusa None 18Programozási nyelvek és paradigmák összehasonlítása 2. GY type Book = { Name : string; AuthorName : string; Rating : int option; ISBN : string; } A Book típus ugyanaz, mint korábban, de ebben az esetben nem kötelező konkrét értékelést adni let unratedEdition = { Name = ”Expert F#”; AuthorName = ”Don Syme, Adam Granicz, Antonio Cisternino”; Rating = None; // konkrét érték esetén például: Rating = Some 5; ISBN = ” ”; }
Mivel az előbb definiált Book típus opcionális értékelés mezővel rendelkezik, ezért amikor egy Book típusú értéket használnánk, meg kell vizsgálni a Rating mező értékének meglétét Ezt megtehetjük mintaillesztéssel, a match kulcsszó használatával 19Programozási nyelvek és paradigmák összehasonlítása 2. GY let printRating book = match book.Rating with | Some rating -> printfn ”A könyv értékelése: %d/5.” rating | None -> printfn ”A megadott könyv nincs értékelve.”
A diszkriminánsos unió típusok olyan adatokat reprezentálnak, amelyek felvehetnek egy vagy több (akár különböző típusú) értéket is Az Option típus valójában egy egyszerű diszkriminánsos unió típus 20Programozási nyelvek és paradigmák összehasonlítása 2. GY type Option = | Some of ‘a | None Saját unió típust is definiálhatunk bonyolultabb problémák megoldására type Shape = | Circle of float // az érték a sugár | EquilateralTriangle of double // az érték az oldalhossz | Square of double // az érték az oldalhossz | Rectangle of double * double // az értékek a magasság és a szélesség
Ha ki akarjuk számítani az előbb definiált alakzatok területét, ahhoz ismét a mintaillesztést használhatjuk Ezzel elkerülhetjük egy kisebb osztályhierarchia létrehozását, illetve azt, hogy minden konkrét alakzatra egy területszámító metódust készítsünk 21Programozási nyelvek és paradigmák összehasonlítása 2. GY let pi = let area shape = match shape with | Circle radius -> pi * radius * radius | EquilateralTriangle s -> s * s * (sqrt 3.0) / 4.0 | Square s -> s * s | Rectangle (h, w) -> h * w Ezt követően egy konkrét kör területének kiszámítása az alábbi módon történik let radius = 15.0 let circle = Circle radius printfn ”%f sugarú kör területe: %f” radius (area circle)
A felsorolási típusok segítségével egy nevet értékek egy halmazához rendelhetünk hozzá Ezek a típusok valójában integral típusok Szintaxisa nagyon hasonlít a diszkriminánsos unió típushoz Fontos különbség azonban, hogy amikor a felsorolási típus egy értékére hivatkozunk, akkor ezt minősíteni kell a típus nevével 22Programozási nyelvek és paradigmák összehasonlítása 2. GY type Color = | Red = 0 | Green = 1 | Blue = 2 let col1 = Color.Red // felsorolási típus használata let n = int col1 // konverzió egy integral típusra
Az F# tömbjei fix méretű, 0 indexbázisú, módosítható gyűjteményei egymás után következő, azonos típusú adatoknak Hasonlóan a listákhoz, a tömbök létrehozása is egyszerű szintaxissal történik 23Programozási nyelvek és paradigmák összehasonlítása 2. GY > let array1 = [|1; 2; 3|];; val array1 : int [] = [|1; 2; 3|] Bonyolultabb tömbök készíthetők sorozatkifejezésekkel let array2 = [| for i in > i * i |] A tömb egy elemére való hivatkozás mellett tömbszeleteket is le tudunk kérdezni a.. operátorral let element = array1.[0] // hivatkozás az első elemre: 1 let slice = array1.[1..3] // hivatkozás tömbszeletre: [|4; 9; 16|]
A rec kulcsszó használata a let kulcsszóval együtt lehetővé teszi rekurzív függvények definiálását A rec kulcsszóval jelezzük, hogy a függvény azonosítójára a függvény törzsében kívánunk hivatkozni 24Programozási nyelvek és paradigmák összehasonlítása 2. GY type BinTree = | Leaf | Node of int * BinTree * BinTree let rec sumTree tree = match tree with | Leaf -> 0 | Node (value, left, right) -> value + (sumTree left) + (sumTree right) let myTree = Node (2, Node (1, Leaf, Leaf), Node (3, Leaf, Leaf)) printfn ”myTree elemeinek összege: %d” (sumTree myTree)
Az osztály definíciójában az as kulcsszó opcionálisan használható arra, hogy a mindenkori objektumpéldánynak tetszőleges nevet adjunk A class és end kulcsszavak jelzik az osztálydefiníció elejét és végét, de ezek megadása szintén nem kötelező Minden F# osztályban van egy elsődleges konstruktor, amelynek paramétereit a definíció fej részében fel kell tüntetni Ennek a konstruktornak a törzse let és do kifejezésekből áll További konstruktorok hozzáadása a new kulcsszóval történik Az új konstruktorok törzsében áthívás történik az elsődleges konstruktorra 25Programozási nyelvek és paradigmák összehasonlítása 2. GY
Az osztályok közötti öröklődés definiálása az inherit kulcsszóval történik és egy osztálynak legfeljebb egy ősosztálya lehet Ha nem adunk meg ősosztályt, akkor az implicit módon az Object osztályból származik Ha egy osztály egy másikból származik, akkor az ősosztály metódusai és műveletei elérhetőek a származtatott osztályban A let kifejezések és a konstruktor paraméterek az osztályra nézve privátak Az alosztályban a base kulcsszóval hivatkozhatunk az ősosztály egy példányára Virtuális metódus deklarálására az abstract kulcsszó használható függetlenül attól, hogy van-e megadva alapértelmezett viselkedés vagy nem A származtatott osztályban az ősosztály egy virtuális metódusa az override kulcsszóval felüldefiniálható 26Programozási nyelvek és paradigmák összehasonlítása 2. GY
Definiáljuk az absztrakt alakzat osztályt, amelynek egyetlen területszámító metódusát a belőle származtatott kör osztályban adjuk meg 27Programozási nyelvek és paradigmák összehasonlítása 2. GY [ ] type Shape() as baseShape = class abstract member area : double end type Circle(r : float) = inherit Shape() let pi = let radius = r override baseShape.area = radius * radius * pi let circle = Circle(5.0) printfn ”A kör területe: %f” (circle.area)
Hasonlóan az osztályokhoz, az interfészek elejét és végét az interface és end kulcsszavak jelzik, de megfelelő tördeléssel ezek megadása nem kötelező Az interfész minden metódusa absztrakt, ezeket az implementáló osztály valósítja meg Az interfész egy metódusának meghívása csak az interfész típusán keresztül történhet és nem azon az objektumon keresztül, amelynek osztálya implementálja az interfészt Ilyenkor használnunk kell az upcast ( :> ) operátort 28Programozási nyelvek és paradigmák összehasonlítása 2. GY type IPrintable = abstract member Print : unit -> unit type Circle(r : float) = inherit Shape() let pi = let radius = r interface IPrintable with member this.Print() = printfn ”%f sugarú kör” radius override baseShape.area = radius * radius * pi let circle = Circle(5.0) (circle :> IPrintable).Print()
Prognyelvek portál: Try F#: F# Language Reference: us/library/dd aspxhttp://msdn.microsoft.com/en- us/library/dd aspx F# Language Specification: F# Software Foundation: Programozási nyelvek és paradigmák összehasonlítása 2. GY
30Programozási nyelvek és paradigmák összehasonlítása 2. GY