Sadržaj

1. Uvod………………………………………………………………………………………………………3

Biografija…………………………………………………………………………………………………….3

Šta je F#?……………………………………………………………………………………………………3

2. Instalacija i počeci………………………………………………………………………………………4

3. Osnovne naredbe……………………………………………………………………………………….5

Naredbe povezivanja let……………………………………………………………………..…..………..5

Funkcija kao tip podataka…………………………………………………………………………………6

Tip podataka n-torka……………………………………………………………………………………….7

4. Tip podataka liste……………………………………………………………………………………….8

Kreiranje liste………………………………………………………………………………………………..8

Rad sa listama………………………………………………………………………………………………9

Naredba match…………………………………………………………………………………………….10

Uparivanje prema obrascu……………………………………………………………………………….12

Tip podataka sekvenca……………………………………………………………………………………13

Generisanje sekvenci………………………………………………………………………………………14

5. Funkcije i njihova primjena…………………………………………………………………………….15

Funkcionalni literali…………………………………………………………………………………………15

Funkcije projekcije, selekcije i agregacije……………………………………………………………….16

6. Kompleksni funkcionalni tipovi podataka……………………………………………………………21

Tip podataka slog (record)………………………………………………………………………………….21

7. Imperativno programiranje……………………………………………………………………………..27

Promjenljive strukture……………………………………………………………………………………….27

Nizovi………………………………………………………………………………………………………….29

Promjenljive kolekcije………………………………………………………………………………………31

Ciklusi…………………………………………………………………………………………………………33

8. Objektno orjentisano programiranje………………………………………………………………….37

Klasa…………………………………………………………………………………………………………..37

9. Napredni koncepti………………………………………………………………………………………..42

Indeksiarno svojstvo…………………………………………………………………………………………42

Imenovani i opcioni argumenti……………………………………………………………………………..43

Abstraktna klasa……………………………………………………………………………………………..45

Modul…………………………………………………………………………………………………………..45



1. Uvod

Biografija


Autor F# je Don Syme. Don Syme se pridružio Microsoft Research-u 1998 godine. Prije toga, bio je student na univerzitetu u Cambridge-u na odsjeku računarskih istraživanja nakon čega je diplomirao na Australijskom nacionalnom univerzitetu 1993 godine.

Bio je glavni saradnik na izradi i provedbi programa Microsoft.Net, C# 2.0, i dizajner F#-a.

Razvijao je  programske fondacije za naučno- programsku revoluciju, dok je sudjelovao u nekoliko Microsoftovih naučnih inicijativa, uključujući 2020 inicijativu naučno-tehničkog računarstva.


 

Šta je F# ?

 

F# je programski jezik, razvijen na .NET platformi, sa izraženim funkcionalnim karakteristikama koji podržava i druge paradigme programiranja poput imperativne i objektno-orijentisane.

Imperativno programiranje je programska paradigma koja opisuje računanje kao izraze koji mijenjaju stanje programa. Kao što se u govornom jeziku zapovijedni način (ili imperativ) koristi za izražavanje naredbi, tako se imperativni programi mogu posmatrati kao niz naredbi koje računar treba izvršiti.

Snaga F#-a leži u svedenoj sintaksi koja omogućava laku čitljivost koda, kao i efikasan razvoj programa koji zahtevaju primenu složenih matematičkih algoritama. Jezik omogućava brzo generisanje prototipova i njihovu brzu transformaciju u produkcioni kod. Kod napisan u F#-u lako se može paralelizovati, što je posebno značajno danas kada svi novi računari imaju više jezgara. Korištenjem F#-a kao osnovnog jezika mogu se lako kreirati domensko specifični jezici ( DSL ) što ga čini posebno pogodnim za naučno-istraživačku primjenu.

F# je kompatibilan sa svim ostalim .NET jezicima. To znači da F# program može da koristi sve .NET biblioteke. Biblioteka napisana u F#-u može se koristiti u bilo kojoj desktop ili Web aplikaciji razvijenoj na .NET platformi.

2. Instalacija i počeci

 

Kako biste radili u F# potrebno je da imate instaliran kompajler i interpreter.

U slučaju ako posjedujete Visual Studio 2010 nije potrebna nikakva dodatna instalacija.

Ukoliko posjedujete Visual Studio 2010, F# konzolu možemo pokrenuti iz Start menija, kao što je prikazano na slici :

Kada se konzola pokrene potrebno je da otkucate fsi i pritisnete taster Enter da bi pokrenuli F# interpreter. Kako bi testirali da li F# interpreter radi otkucajte printfn “Hello World“;;. Dobićete prikaz kao na sledećoj slici:

3. Osnovne naredbe

 

Naredbe povezivanja let

Naredba let je najznačajnija i najčešće korišćena naredba u F#-u. Služi da poveže identifikatore sa vrednostima. Sledeći primer ilustruje upotrebu ove naredbe.

Kao što možete da uočite naredba let ima formu: let ident = expr, gde je ident identifikator (u ovom slučaju „a“), dok je expr izraz ( u ovom slučaju vrijednost „21“).

Treba primjetiti da je F# interpretatora automatski odredio da je vrijednost povezana sa identifikatorom „a“ cjelobrojna ( int ). Ova funkcionalnost F# interpretatora naziva se inferencija tipa.

Druga stvar koju ste možda primjetili je upotreba riječi identifikator za simbol „a“, umjesto možda očekivane riječi promjenljiva.

Razlog za to je što se simboli u F# podrazumjevano dodjeljuju vremenski nepromjenljivim (eng. immutable) vrijednostima.

Ukoliko pomoću naredbe let povežemo identifikator „a“ sa nekom drugom vrijednošću, neće se promjeniti sadržaj memorijske lokacije prethodno povezane sa identifikatorom „a“, već će identifikator „a“ biti povezan sa drugom memorijskom lokacijom.

Da bi to ilustrovali povezaćemo identifikator „a“ sa promjenljivom tipa znak.

Pored cjelobrojnog i znakovnog u F# možemo koristiti i vrijednosti osnovnih tipova poput realnog broja (float), niske znakova (string) i logičkog (bool).

 

Funkcija kao tip podataka

 

Funkcije su u F#-u samo jedan od tipova podataka kao što je to slučaj sa cjelobrojnim ili znakovnim tipovima. Šta više možemo definisati i vrijednost funkcijskog tipa, funkcijski literal.

Svaki funkcijski literal ima svoj tip, koji interpreter automatski utvrđuje kao što je to slučaj i kod ostalih tipova. Funkcijski literal (fun x-> x*x) ima tip „int->int“ što označava da predstavlja funkciju koja preslikava skup celih brojeva na skup celih brojeva.

 

Naredba povezivanja funkcijske vrednosti sa identifikatorom ima i svoju kraću, prirodniju, formu.

Ukoliko nismo zadovoljni tipom funkcije koji je interpreter automatski odredio, možemo mu dati sugestiju (eng. compiler hint). U primjeru koji slijedi ova sugestija je namjerno data u sredini izraza kako je čitalac ne bi pomješao sa definisanjem liste parametara.

Tip podataka n-torka

 

N-torka (eng. tuple) je najjednostavniji i najčešće korišteni od svih složenih tipova podataka. Koristimo ga kada želimo da koristimo uređeni skup vrijednosti, a da pri tome eksplicitno ne definišemo tip.

Slijedeći primjer prikazuje povezivanje uređenog para gdje je prvi član tipa string, a drugi tipa int sa identifikatorom “s”.

 

Uređene parove možemo vezati i za više identifikatora, pri čemu se identifikatori vezuju za vrijednosti prema poziciji. Ukoliko neku od vrijednosti iz uređenog para ne želimo da vežemo za neki identifikator, umjesto identifikatora stavljamo simbol “_”.

Navedeni oblici let naredbe ilustrovani su sledećim primerom.


 

4. Tip podataka lista

 

Kreiranje liste

 

Liste su redovni pratilac funkcionalnih programskih jezika. Neki jezici poput Lisp-a koncipirani su oko rada sa listama.

Proučavanje lista počećemo sa sljedećim naredbama:

1.) Prva naredba predstavlja literal za praznu listu. Kompajler naravno ne može da odredi kog tipa je prazna lista pa automatski definiše generalizovani tip.

2.) Druga naredba predstavlja literal za listu cijelih brojeva. Iako to nigdje eksplicitno nismo naveli kompajler je ispravno odredio tip literala kao int list.

Treća i četvrta naredba definiše listu preko intervala, pri čemu je u četvrtoj definisan i korak sa kojim se članovi generišu.

Liste mogu biti i nekog drugog tipa, npr. tipa string. Međutim ono što je važno napomenuti je da svi članovi liste moraju biti istog tipa. U suprotnom će kompajler prijaviti sintaksnu grešku.

Sljedeći primjer ovo pokazuje:


Rad sa listama

 

Kalkulacije sa listama izvršavaju se primjenom operatora i funkcija na liste.

Operatori za rad sa listama su:

  • operator spajanja elementa i liste (::)
  • operator spajanja dvije liste (@)

Kao i većina osnovnih tipova u F#-u liste su nepromjenljive (eng. immutable). Sve operacije nad listama kreiraju nove liste umjesto da mijenjaju postojeće.

Sljedeći primjer prikazuje listu l kojoj smo dodjelili literal  [9;10]. Poslije dodavanja vrijednosti 8 na listu l, data lista je ostala nepromjenjena što je pokazano izvršavanjem naredbe l;;.

Pored operatora, F# biblioteke sadrže i veći broj funkcija za rad sa listama. U ovom poglavlju prikazaćemo samo osnovne funkcije i to:

  • List.length       vraća dužinu liste
  • List.head         vraća prvi element liste
  • List.tail            vraća sve elemente liste osim prvog

 

Pored navedenih, od izuzetnog značaja za rad sa listama su i funkcije:

 

  • List.map          funkcija projekcije
  • List.filter         funkcija selekcije
  • List.fold           funkcija agregacije

Naredba match

 

Osnovni oblik

Naredba match je naredba koja u sebi kombinuje dekompoziciju složenih vrijednosti i kontrolu toka.

U svom osnovnom obliku podsjeća na switch naredbu iz jezika C++, i tada uparuje proslijeđenu vrijednost sa nekim od datih literala. Zavisno od literala koji je uparen izvršava se odgovarajuci segment koda.

Kao i kod switch naredbe jedan segment koda je moguće vezati za više literala.

Za razliku od switch naredbe, naredba match  (kao i sve ostale F# naredbe) vraća vijrednost. U prethodnom primjeru je ta vrijednost prikazana sa “val it : unit = ()” ,što je tip povratne vrijednosti funkcije printfn.

Sljedeći primjer ilustruje naredbu match koja umjesto prikazivanja teksta u konzoli vraća tekst kao rezultat.

Uparivanje prema obrascu

 

Naredba match nije ograničena na uparivanje sa literalima,  već je moguće da se uparivanje vrši prema obrascu. Jedan od obrazaca za uparivanje već je prikazan, iako to nije posebno istaknuto. To je obrazac za uparivanje “_”, koji se može upariti sa bilo kojom vrijednošću (odgovara default grani switch naredbe).

Sljedeći primjer ilustruje uparivanje sa obrascem za listu, pri implementaciji funkcije getFirst koja je ekvivalentna funkciji List.head.

Obrazac za uparivanje može biti ograničen logičkim izrazom. U tom slučaju do uparenja sa odgovarajućom granom dolazi samo ako logički izraz ima vrijednost true (tačan izraz).

Sljedeći primjer ilustruje primjenu ograničenja na primjeru implementacije funkcije round koja preslikava cio broj na broj iz intervala [0,100].

Možete uočiti kako je obrazac za uparivanje bilo koje vrednosti “_”, ograničen izrazima x>100 i x<0.

Tip podataka sekvenca

Koncept

Sekvenca predstavlja skup vrednosti istog tipa. Prema operacijama koje se nad njom mogu primeniti slična je listi. Jednostavne sekvence se definišu na sličan način kao i liste što pokazuje sledeći primjer:

Ono što sekvencu razlikuje od liste je njena deklarativna priroda. Naime sekvenca ne čuva u memoriji svoje članove već samo generatorski izraz (eng. sequence expression) koji prema potrebi generiše date elemente sekvence.

Mogli ste da primjetite da se za potrebe prikazivanja u konzoli generišu samo prva četiri elementa sekvence.

Generisanje sekvenci

 

Generatorski izraz ima opšti oblik seq { for value in expr .. expr do yield expr}.

Sljedeći primjer ilustruje generisanje sekvence kvadrata prirodnih brojeva iz intervala

[1,100], primjenom obje sintaksne forme.

Sekvence se mogu ugnježđavati, tako da rezultat primjene generatorskog izraza može biti sekvenca sekvenci.

Ukoliko želimo da umesto ugnežđenih sekvenci kao rezultat dobijemo kompoziciju sekvenci, potrebno je da umesto yield naredbe upotrebimo naredbu yield!.

Izraz za generisanje sekvenci može biti proizvoljne složenosti i sadržati u sebi druge naredbe. Sljedeći primjer ilustruje sekvencu od 100 uređenih parova, pri čemu su neparni članovi sekvence zamjenjeni uređenim parom (0,0).

5. Funkcije i njihova primena

Funkcionalni literali

Programski jezik F# tretira vrijednost funkcije nezavisno od njenog identifikatora (naziva). Pokazano je da izraz poput “let sq x = x * x”, predstavlja samo skraćenu formu koja objedinjuje definisanje funkcionalnog literala “(fun x-> x * x)” i njegovo vezivanje sa identifikatorom “sq”.

Funkcionalni literali mogu se upotrijebiti na bilo kom mjestu, na kojem se može upotrijebiti vrijednost nekog drugog tipa, poput argumenta koji se prosljeđuje drugoj funkciji ili operanda datog operatora.

Funkcionalni literali često se primjenjuju u kombinaciji sa funkcijama projekcije, selekcije i agregacije. Ove tri funkcije, koje služe za transformaciju liste ili sekvence čine jedan od najmoćnijih alata “klasičnog” funkcionalnog programiranja.

Funkcije projekcije, selekcije i agregacije

Seq.map

Seq.map je funkcija projekcije koja na osnovu postojeće, kreira novu sekvencu primjenom date funkcije. Svaki element u generisanoj sekvenci je rezultat izvršavanja funkcije nad elementom postojeće sekvence.

Seq.filter

Seq.filter je funkcija selekcije koja kreira novu sekvencu filtriranjem postojeće, korišćenjem predikatske funkcije.

Sljedeći primjer ilustruje selekciju elemenata sekvence čiji je prvi član paran broj:,

U  prethodnom primjeru korišćen je funkcionalni literal kao argument funkcije. Međutim

kao parametar funkcionalnog literala upotrjebljen je obrazac za uparivanje koji predstavlja uređeni par, kod koga se prvi član para uparuje sa identifikatorom „x“, a drugi član se ne uparuje: fun (x,_) -> x%2=0 . Iako drugi član para nije važan, ovakva forma funkcionalnog literala je neophodna zbog kompatibilnosti tipova. Naime sekvenca koja se filtrira je sekvenca uređenih parova, pa prema tome ulazni parametar predikatske funkcije mora biti uređeni par.

Seq.fold

Funkcija agregacije je svakako najmoćnija i ujedno najkompleksnija od svih funkcija za transformaciju sekvenci. Služi za izračunavanje agregirane vrijednosti, primjenom date funkcije na skup vrijednosti (u ovom slučaju sekvencu). Primjena se vrši rekurzivno, tako što funkcija za agregaciju, agregira svaki član sekvence sa prethodno izračunatim rezultatom. Funkcija agregacije prima tri parametra redom: funkciju za agregaciju člana sekvence sa akumuliranim rezultatom, početnu vrijednost akumulacije i sekvencu.

Sljedeći primjer ilustruje izračunavanje sume drugih članova (kvadrata), selektovanih uređenih parova.

Možete zapaziti da funkcija agregacije može zamjeniti naredbu ciklusa (for, foreach) poznatu iz imperativnog i objektno orijentisanog programiranja.

Operatori za rad sa funkcijama

F# definiše više operatora za rad sa funkcijama među kojima su najznačajniji:

  • operator ulančavanja poziva funkcija (|>)
  • operator kompozicije funkcija (>>)

Operator ulančavanja poziva funkcija

Operator ulančavanja poziva funkcija omogućava programeru da pozove funkciju na takav način da, u programskom kodu, posljednji argument funkcije napiše prije njenog naziva. Ovakav način pisanja koda omogućava nam ulančavanje poziva funkcija.

Operator kompozicije funkcija

Operator kompozicije funkcija omogućava kreiranje nove funkcije koja predstavlja kompoziciju operanada.

Mogućnosti prikazanih operatora posebno dolaze do izražaja u kombimnaciji sa funkcijama projekcije, selekcije i agregacije. Sljedeći primjer ilustruje navedeno, na primjeru rješavanja problema računanja sume kvadrata brojeva od 1 do 1000 u jednoj liniji koda:

Parcijalna primjena funkcije

Parcijalna primjena funkcije (engl. function currying) je mogućnost kreiranja nove funkcije pozivanjem postojeće, pri čemu se neki argumenti izostavljaju.

Primjer koji slijedi, ilustruje kreiranje funkcija „inc“ (koja uvećava dati broj za jedan) i „dec“ (koja umanjuje dati broj za jedan), parcijalnom primjenom funkcije za izračunavanje zbira dva broja „add“.

Parcijalna primjena funkcije postaje posebno korisna u kombinaciji sa funkcijama projekcije, selekcije ili agregacije. Sljedeći primjer ilustruje jednostavno kreiranje funkcije za sumiranje elemenata sekvence:

Rekurzivne funkcije

Funkcija koja u svom tijelu poziva samu sebe naziva se rekurzivnom. Tipična rekurzivna funkcija je faktorijel (n! = n x (n-1)!). Rekurzivne funkcije se u F#-u moraju posebno označiti upotrebom ključne reči rec ispred naziva funkcije.

Ukoliko imamo dve funkcije koje međusobno pozivaju jedna drugu, onda kažemo da su te dve funkcije međusobno rekurzivne. U F# kodu je neophodno da međusobno rekurzivne funkcije budu definisane neposredno jedna za drugom i spojene pomoću ključne reči and. To omogućava F# kompajleru da ove dve funkcije posmatra kao jednu cjelinu sa stanovišta automatskog utvrđivanja tipa.

Operatori

Operatori u F#-u su specijalne funkcije čiji se nazivi sastoje od simbola: „!%&*+-./<=>?@^|~:“, pri čemu simbol „:“ ne može biti na prvoj poziciji. Naziv operatora se prilikom definisanja funkcije piše između zagrada. Primjena operatora značajno povećava čitljivost koda.

6. Kompleksni  funkcionalni tipovi podataka

 

Tip podataka slog (record)

Tip podataka slog omogućava programeru da grupiše više vrijednosti u vrijednost jednog tipa. Za razliku od n-torke slog omogućava imenovanje datih vrijednosti. Imenovana vrijednost u okviru sloga se naziva polje.

Slog definišete navođenjem parova naziv/tip.

Pošto ste definisali tip možete definisati i vrijednosti datog tipa, navođenjem vrijednosti za svako polje.

Možete uočiti da je F# kompajler automatski izvršio inferenciju tipa identifikatora “s”.

Vrijednostima koje se nalaze u slogu možete pristupiti primjenom operatora pristupa polju “.”.

Ukoliko želite da definišete više slogova koji se razlikuju samo po vrednosti jednog polja možete upotrebiti tehniku kloniranja sloga.

Za kreiranje slogova se često koristi i generatorska funkcija koja omogućava korišćenje jednostavnije sintakse za kreiranje slogova. Pored toga u okviru same funkcije moguće je izvršiti transformaciju argumenata u vrijednosti polja sloga. Sljedeći primjer ilustruje ovu tehniku:

Tip podataka unija diskriminatora (discriminated union)

Unija diskriminatora predstavlja tip podataka čija vrijednost može biti u skladu sa samo jednim od definisanih diskriminatora. U svom najjednostavnijem obliku diskriminator predstavlja jednu vrijednost a unija diskriminatora skup vrijednosti. Tada tip unija diskriminatora semantički podsjeća na tip “enumeracija” u programskom jeziku C#.

Za razliku od elementa enumeracije diskriminatoru možemo pridružiti vrijednost proizvoljnog tipa, pri čemu dati tip ne mora biti isti za sve diskriminatore. Sljedeći primjer prikazuje definisanje unije diskriminatora koja opisuje prijevozno sredstvo. Pri tome je automobilu pridružen par niski znakova koje označavaju marku i model, autobusu je pridružen broj linije, a biciklu nije pridružen nikakav podatak.

Prilikom definisanja vrijednosti tipa unija diskriminatora navodimo samo diskriminator, a kompajler vrši inferenciju tipa.

Vrijednost tipa unije diskriminatora se može uparivati prema obrascu. Pri tome se uparivanje može vršiti na osnovu diskriminatora i pridruženih vrijednosti.

Unija diskriminatora je tip podataka koji nam omogućava da pišemo veoma efikasan funkcionalni kod. Sljedećih nekoliko primjera ilustruju njegovu primjenu.

Modelovanje strukture podataka

Prvi primjer prikazuje modelovanje strukture podataka pomoću unije diskriminatora koja ima samo jedan diskriminator za koga su vezani podaci.

Prednost ovakvog rješenja u odnosu na slog je uočljiva na primjeru pisanja funkcije koja kao argument prima strukturu Point3D.

Možete uočiti da je navedeni argument definisan preko segmenata diskriminatora Vector3D čime je omogućeno da telo funkcije bude napisano kratko i jasno.

Modelovanje struktura u obliku stabla

Unije diskriminatora su idealne za modelovanje struktura u obliku stabla kao i za implementaciju operacija nad njima.

Za ovako definisano binarno stablo jednostavno je napisati rekurzivnu funkciju koja prolazi kroz njega. Sledeći primer prikazuje funkciju koja vraća broj čvorova u stablu.

Modelovanje domenski specifičnih jezika

Sintaksa programskog jezika F# sadrži više sintaksnih elemenata koji omogućavaju modelovanje domenski specifičnih jezika. Modelovanje domenski specifičnog jezika pomoću unije diskriminatora je veoma jednostavno, mada složeniji problemi iz date oblasti zahtjevaju upotrebu drugih sintaksnih elemenata i drugačiji pristup.

Sljedeći primjer ilustruje primjenu unije diskriminatora za modelovanje predikatske logike:

Tip podataka opcija (option)

Opcija je tip podataka koji služi za modelovanje vrijednosti koje mogu ali ne moraju da budu postavljene. Predstavlja specifičan slučaj unije diskriminatora definisane kao:

type ‘a option = None | Some of ‘a

Tip opcija se najčešće upotrebljava za povratnu vrijednost funkcije koja nema definisan izlaz za sve vrijednosti ulaza.

Tipičan primjer takve funkcije je funkcija koja konvertuje nisku znakova u cio broj. Ukoliko niska znakova ne predstavlja broj, funkcija ne može da vrati smislen rezultat.

Biblioteke programskog jezika F# sadrže veći broj funkcija za rad sa opcijama. Najznačajnije od njih su:

  • Option.isSome              vraća true ukoliko argument ima vrijednost, inače false
  • Option.isNone              vraća true ukoliko argument nema vrijednost, inače false
  • Option.get                    vraća vrijednost ukoliko ona postoji, inače izbacuje     izuzetak

Sljedeći primjer ilustruje upotrebu navedenih funkcija:

7. Imperativno programiranje

Pod imperativnim programiranjem se podrazumjeva pisanje programa, koji tokom izvršavanja više puta upisuju podatke na istu memorijsku lokaciju. U programskim jezicima, kao što su C++, C# ili Visual Basic to je potpuno uobičajno. Nasuprot, tome naredba let u F#-u prilikom svakog povezivanja vrijednosti sa identifikatorom upisuje vrijednost u novu memorijsku lokaciju.

Naravno, ponekad postoji potreba da se i u F# programima piše imperativni kod. To je posebno slučaj kad se postojeći, složeni algoritmi, prevode sa nekog imperativnog programskog jezika (poput C++a ili Fortrana) prenose u F#.

Promjenljive strukture

 

Osnovu imperativnog programiranja čine tipovi podataka, definisani tako da omogućavaju promjenu sopstvenih instanci kroz promjenu memorijskog prostora koje instance zauzimaju.

Sintaksa programskog jezika F# razlikuje tri vrste promenljivih struktura, i to:

  • Lokalne promjenljive
  • Reference
  • Slogove sa promjenljivim poljima

Lokalne promjenljive

Lokalne promjenljive definišu se na sličan način kao i identifikatori, pri čemu se posle naredbe povezivanja let upotrebljava ključna reč mutable. Promjena lokalne promenljive vrši se pomoću operatora strelica ulevo  (<-).

Lokalne promjenljive imaju određena ograničenja, a najznačajnije od njih je da se mogu koristiti samo u sopstvenoj oblasti definisanosti ( npr. prilikom vraćanja lokalne promjenljive iz funkcije ne vraća se sama lokalna promjenljiva, nego kopija njene vrijednosti).

Zbog toga se umjesto lokalnih promjenljivih često koriste reference (engl. reference cells).

Reference

Referenca predstavlja objektni omotač (ćeliju) u kojoj je smeštena vrijednost koja se može mjenjati tokom izvršavanja programa. Vrijednost se smešta u referencu primjenom funkcije ref.

Sadržaju reference pristupa se pomoću operatora (!), dok se za izmjenu sadržaja koristi operator (:=).

Slogovi sa promjenljivim poljima

Sintaksa programskog jezika F# omogućava definisanje slogova sa promjenljivim poljima. Promjenljiva polja definišu se pisanjem ključne riječi mutable ispred naziva polja. Sljedeći primjer ilustruje korišćenje sloga sa promjenljivim poljima kroz implementaciju imenovanog brojača.

Nizovi

 

Niz (engl. array) je kolekcija promjenljivih elemenata istog tipa. Implementacija niza je takva da su elementi koji ga čine smješteni u memoriji neposredno jedan posle drugog. Zbog toga je pri deklaraciji niza potrebno tačno znati koliko će elemenata biti u nizu. Prednost ovakve implementacije je u tome što omogućava efikasan pristup proizvoljnom elementu niza.

Nizovi se mogu kreirati pomoću generatorskih izraza, poput lista i sekvenci, ili zadati kao lista vrijednosti odvojenih znakom ; između znakova [| i |].

Elementima niza pristupa se pomoću operatora indeksiranja (.[]) . Operator indeksiranja upotrebljava se kako kod čitanja tako i kod mjenjanja sadržaja elementa niza.

Operator indeksiranja omogućava izdvajanje podniza kada se koristi u sintaksnoj formi arr.[donjaGranica .. gornjaGranica] . Ukoliko donja granica nije specificirana, za donju granicu se uzima prvi element niza. Slično tome, ukoliko gornja granica nije specificirana, za gornju granicu se uzima posljednji element niza.

F# biblioteke sadrže i veći broj funkcija za rad sa nizovima. U ovom poglavlju prikazaćemo samo osnovne funkcije i to:

  • Array.length                  vraća dužinu niza
  • Array.zeroCreate          kreira niz popunjen podrazumjevanim vrijednostima elementa za dati tip
  • Array.append                omogućava spajanje dva niza

Pored navedenih, od izuzetnog značaja za rad sa nizovima  su i funkcije projekcije selekcije i agregacije:

  • Array.map                    funkcija projekcije
  • Array.filter                    funkcija selekcije
  • Array.fold                     funkcija agregacije

koje su semantički ekvivalentne odgovarajućim funkcijama za rad sa listama.

Promenljive kolekcije

 

Promenljive kolekcije, pored mogućnosti izmjene elemenata koje se u njima nalaze, imaju i mogućnost dodavanja i uklanjanja elemenata. Mogućnost promjene veličine kolekcije tokom vremena, je značajna u situacijama kada unaprijed nije poznato koliko će elemenata biti u kolekciji ( npr. kada se u kolekciju smješta rezultat upita u bazu podataka ).

Promjenljive kolekcije nisu specifične za F#, već se nalaze u standarndnim .NET bibliotekama u prostoru imena System.Collections.Generic.

List<’T>

Kolekcija List<’T> predstavlja niz promjenljive dužine. Interna implementacija ove kolekvcije je zasnovana na nizovima, tako da je pristup elementima kolekcije veoma efikasan.

Pošto se kolekcija List<’T> nalazi u prostoru imena System.Collections.Generic prije kreiranja same kolekcije potrebno je uključiti navedeni prostor imena.

U kolekciju je moguće dodati element korišćenjem metode Add, odnosno sekvencu elemenata korišćenjem metode AddRange. Broj elemenata koji se nalaze u kolekciji može se očitati pomoću svojstva Count.

Uklanjanje elementa iz kolekcije vrši se pomoću metode Remove. Ova metoda vraća logičku vrijednost true ukoliko je element uklonjen iz kolekcije, a logičku vrijednost false ukoliko se element nije ni nalazio u kolekciji.

Elementi liste mogu se mijenjati na isti način kao i elementi niza.

Pored navedenih, metoda i svojstava od značaja za rad sa kolekcijama tipa List<’T> su i sledeće metode:

  • Contains           Vraća logičku vrijednost true ako je element sadržan u kolekciji
  • IndexOf            Vraća indeks elementa u kolekciji. Ako se element ne nalazi u kolekciji vraća -1
  • Insert                Dodaje element u kolekciju, na lokaciju sa datim indeksom


Dictionary<’K,’V>

Kolekcija Dictionary<’K,’V>  predstavlja asocijativni niz. Koristi se kada je potrebno elementima pristupiti po asociranoj vrijednosti (ključu), umjesto po indeksu.

Asocirana vrijednost za dati ključ očitava se pomoću operatora indeksiranja koji prima ključ kao parametar. Ukoliko sa datim ključem nije asocirana neka vrijednost, dolazi do pojave izuzetka i eventualno do prekida programa.

Kad god postoji mogućnost da sa datim ključem nije asocirana vrijednost, poželjno je koristiti metodu TryGetValue. Ova metoda vraća uređeni par. Prvi član uređenog para je logičkog tipa i ima vrijednost true ili false, zavisno od toga da li je sa datim ključem asocirana neka vrijednost ili ne. Drugi član uređenog para je tražena vrijednost, odnosno specijalna vrijednost null, ukoliko ne postoji vrijednost koja je asocirana sa datim ključem.

Ciklusi

 

Imperativno programeranje podrazumjeva eksplicitnu kontrolu toka izvršavanja programa. Takva kontrola podrazumjeva i upotrebu ciklusa koji omogućavaju repetativno izvršavanje određenog djela koda, određeni broj puta, odnosno do ispunjenja datog uslova.

Programski jezik F# podržava while ciklus i dve vrste for ciklusa.

While ciklus

While ciklus se izvršava sve dok uslov ciklusa vraća logičku vrijednost true. Sljedeći primjer ilustruje primjenu while ciklusa u funkciji za izračunavanje zbira cjelih brojeva u zatvorenom intervalu [n,m].

For ciklus

For ciklus se koristi kada je unaprijed poznat broj iteracija koje treba da budu izvršene.

Jednostavna forma for ciklusa podrazumjeva postojanje cjelobrojne ciklusne promjenljive (brojača) u zaglavlju ciklusa, koja uzima vrijednosti između donje i gornje granice. Sljedeći primjer je u funkcionalnom smislu ekvivalentan prethodnom samo što je ovaj put za rješavanje problema upotrjebljena jednostavna forma for ciklusa.

Izuzeci

Da bi se obezbjedila pouzdanost programa neophodno je vješto upravljanje neočekivanim ili izuetnim situacijama. Pod neočekivanim i izuzetnim situacijama podrazumjeva se neispravnost podataka, uređaja ili nedostupnost servisa ( npr. Internet konekcije ). Ukoliko se dogodi neka od naprijed opisanih situacija, u .NET programu dolazi do genereisanja izuzetka (engl. exception).

Izuzetak je neispravnost u .NET programu, koja prekida normalni tok programa, tako što dolazi do automatskog prekida svake funkcije u lancu poziva, sve do obrade izuzetka u segmentu koda za obradu izuzetaka (engl. exception handler). Ako takav segment ne postoji dolazi do prekida programa.


Generisanje izuzetaka

Ukoliko funkcija ne može da vrati odgovarajuću vrijednost za određene vrijednosti argumenata, poželjno je da u tom slučaju generiše izuzetak.

Izuzetci se najjednostavnije generišu pomoću funkcije failwith, koja kao parametar prima odgovarajuću poruku.

Iako je poruka o grešci sama po sebi korisna,u skladu sa pravilima dobre prakse potrebno je generisati tipizirani izuzetak. Tipizirani izuzetci generišu se pomoću funkcije raise. Prije korišćenja tipiziranih izuzetaka neophodno je uključiti prostor imena System.

Čitljivost koda se može dodatno poboljšati definisanjem programski specifičnih (engl. custom) klasa izuzetaka.

Obrada izuzetaka

Obrada izuzetaka vrši se pomoću try-with naredbe. Try-with naredba sastoji se iz try bloka naredbi i with skupa pravila za uparivanje po obrascu. Try blok naredbi sadrži skup naredbi koje mogu generisati izuzetak. Ukoliko neka od naredbi iz try bloka generiše izuzetak, .NET izvršno okruženje će pokušati da upari generisani izuzetak sa nekim od pravila za uparivanje iz with skupa. Ukoliko dođe do uparivanja izvršavaju se naredbe u okviru odgovarajućeg pravila za uparivanje i program nastavlja sa izvršavanjem. U suprotnom prekida se izvršavanje trenutne funkcije, a izuzetak se prosljeđuje sljedećoj funkciji u lancu poziva.

Ukoliko postoji potreba da se poslije izvršenja skupa naredbi izvrši drugi skup naredbi, nevezano za to da li je pri izvršavanju prvog skupa naredbi došlo do generisanja izuzetka ili ne, koristi se  try-finally naredba.

Try-finally naredba sastoje iz try bloka naredbi i finally bloka naredbi. Try blok naredbi sadrži naredbe koje mogu da generišu izuzetak, dok finally blok naredbi sadrži naredbe koje se izvršavaju po izlasku iz try bloka naredbi.

Karakterističan primjer primjene try-finally naredbe je fragment koda koji koristi resurs koji se eksplicitno mora vratiti operativnom sistemu na korišćenje (npr. datoteka).

Za razliku od programskog jezika C# čijom je sintaksom definisana naredba try-catch-finally, sintaksa programskog jezika F# ne poznaje naredbu try-with-finally.

8. Objektno orijentisano programiranje

Najveći deo .NET platforme napisan je u objektno orijentisanim programskim jezicima, prvenstveno u programskom jeziku C#. Zato je poznavanje objektno orijentisanog programiranja (OOP) od ključne važnosti za korišćenje brojnih .NET biblioteka. Cilj ovog poglavlja nije da nauči čitaoca objektno orijentisanom programiranju( to bi bilo previše ambiciozno), već da prikaže kako su objektno orijentisani koncepti utkani u F#.

Objektno orijentisan stil programiranja pogodan je za pisanje kompleksnih softverskih sistema. Navedena pogodnost se ogleda u mogućnosti da se objekti iz realnog sveta modeluju pomoću softverskih abstrakcija, klasa.

Klasa

Klasa je sintaksni element koji povezuje funkcije i podatke. Sintaksa programskog jezika F# definiše više elemenata koji se iz objektno orijentisanih programskih jezika, poput C#-a, „vide“ kao klase. To su:

  • Slog
  • Unija diskriminatora
  • Izuzetak
  • Klasa
  • Modul

Slog, unija diskriminatora i izuzetak prikazani su u prethodnim poglavljima. Ovaj odeljak bavi se sintaksnim elementom koji je označen kao klasa u specifikaciji programskog jezika F#.

Objekat klase Vector2D kreiramo navođenja imena klase i prosljeđivanjem vrijednosti parametara potrebnih za inicijalizaciju objekta.

Broj i tip vrijednosti koje se prosljeđuju prilikom kreiranja objekta klase Vector2D definisan je formom zaglavlja definicije klase:

type Vector2D(dx:float, dy:float)=

Radi boljeg razumjevanja biće prikazana ekvivalentna C# sintaksa:

Možete uočiti da zaglavlje definicije klase u F#-u, u semantičkom smislu, igra ulogu koju ima konstruktor u C#-u, pa se u skladu sa time i naziva implicitni konstruktor. Implicitni konstruktor, uporedo sa parametrima, definiše i privatna polja istog tipa i naziva koje imaju i parametri. Ovako definisana polja mogu se koristiti u svim članovima klase.

Pored polja članovi klase mogu biti svojstva (engl. properties) i metode.

Svojstvo

Svojstva služe za pristup podacima. Najčešće su to “sirovi” podaci, prezentovani na način kako su definisani u poljima, ali mogu biti i izračunate vrijednosti (npr. svojstvo Length zavisi od veličine više polja).

Svojstva DX i DY, iz prethodnog primjera, definisana su korišćenjem skraćene sintakse:

member t.DX = dx

Ovakva sintaksna forma omogućava samo očitavanje vrijednosti svojstva i ekvivalentna je sljedećem C# kodu:

Slično kao i u drugim objektno orijentisanim programskim jezicima, svojstva mogu pripadati klasi umjesto pojedinim objektima date klase. Takva svojstva nazivaju se statička, a u programskom kodu označavaju se pomoću ključne reči static:

static member Zero = Vector2D(0.,0.)

Statička svojstva najčešće se koriste za kreiranje specifičnih objekata date klase (npr. nula vektor).

Metoda

Metoda predstavlja operaciju koju je moguće izvršiti nad objektom. Prilikom definisanja metode u zaglavlju se pored naziva navodi  i skup parametara potrebnih za njeno pozivanje. U primjeru je data metoda Scale koja prima parametar k tipa float i vraća Vector2D objekat čije su dimenzije uvećane k puta.

member t.Scale(k) = Vector2D(k*dx,k*dy)

Ne mjenja se stanje postojećeg objekta već kreira novi objekat inicijalizovan na promjenjeno stanje. Zbog toga za klasu Vector2D kažemo da je nepromjenljiva (engl. immutable).

Iako je preporučeno da F# klase budu nepromjenljive, moguće je napisati i klasu koja tokom izvršenja programa mjenja svoje stanje.

Promenljiva klasa

 

Definisanje promjenljive klase ilustrovano je na primjeru koji je funkcionalno sličan prethodnom:

Možete uočiti da su polja klase eksplicitno definisana i definisana kao promjenljiva, mada se i dalje koristi implicitni konstruktor.

Promjenljiva svojstva su definisana tako da sadrže programski kod za očitavanje (get) i promenu (set):

member t.DX with get()=dx and set(v)=dx <- v

Sada je moguće napisati kod koji menja stanje objekta korišćenjem svojstva i operatora promene stanja (<-).

Posebno interesantna je implementacija funkcije Scale:

member t.Scale(k) = dx <- k*dx; dy <- k*dy; t

Ekvivalentan C# kod izgleda ovako:

 

Posmatranjem C# koda, može se lakše uočiti, da metoda vraća referencu na objekat čiji je i sama član (this). Programski jezik F#, za razliku od C#-a nema rezervisanu ključnu reč za referencu na objekat kome metoda pripada već se naziv reference definiše u zaglavlju metode ispred njenog imena od koga je razdvojen tačkom (npr. t.Scale(k)).

Testiranje metode Scale pokazuje da se njenim pozivom menja objekat nad kojim je metoda pozvana i da identifikator v2 koji je povezan sa povratnom vrednošću metode referiše isti objekat kao i identifikator v sa kojim je objekat povezan prilikom kreiranja.

9. Napredni koncepti

Sljedeći primjer ilustruje napredne koncepte poput indeksiranih svojstava, preklapanja operatora i imenovanih i opcionih argumenata.

Indeksirano svojstvo

 

Indeksirana svojstva (engl. indexers) omogućavaju da se podacima pristupa pomoću operatora indeksiranja (.[ ]). Datu funkcionalnost treba implementirati samo ukoliko ona ima logičkog smisla. U prethodnom primjeru implementacija indeksiranog svojstva omogućava nam da koordinatama vektora pristupimo preko indeksa, pri čemu x koordinati odgovara indeks 0, a y koordinati vrijednost indeksa 1.

Preklapanje operatora

Preklapanje operatora podrazumjeva definisanje operatora (npr. + ili  -) za proizvoljnu korisnički definisanu klasu. Operator se definše implementiranjem statičke metode koja umjesto imena sadrži simbol operatora u zagradama:

     static member (+) (a:Vector2D,b:Vector2D) =  Vector2D(a.DX+b.DX, a.DY+b.DY)


 

Imenovani i opcioni argumenti

 

Sintaksa programskog jezika F#, omogućava programeru, da prilikom prosljeđivanja parametra metodu, imenovanjem poveže vrijednost sa odgovarajućim argumentom.

Mogućnost da se argument proglasi za opcioni, omogućava programeru, da izbjegne definisanje većeg broja funkcionalno istih metoda sa različitim brojem parametara. Argument se označava kao opcioni tako što se ispred njegovog naziva stavi upitnik.

member t.Scale(k,?k2) =
let defK2 = defaultArg k2 1.
dx <- k*defK2*dx; dy <- k*defK2*dy;
t;;

Funkcija defaultArg omogućava definisanje podrazumjevane vrijednosti opcionog argumenta.

Interfejs

Interfejs predstavlja sintaksni element koji omogućava implementaciju „može da“ relacije (engl. can do). Ova relacija označava da objekat date klase može da izvrši skup opercija. Interfejs služi da definiše dati skup operacija bez definisanja njihove implementacije.

Pomoću interfejsa se najčešće povezuju klase koje implementiraju skup operacija na potpuno različite načine. Primjer takvog interfejsa bio bi IShape interfejs koji definiše metodu Contains. Metoda Contains vraća true, odnosno false u zavisnosti od toga da li prosljeđeni par realnih vrijednosti predstavlja koordinate tačke u okviru date figure.

Za razliku od C#-a, programski jezik F# podržava isključivo eksplicitnu implementaciju interfejsa. Sintaksa je prikazana na primeru definisanja klasa Circle i Square koje implementiraju IShape interfejs.

Pošto je implementacija interfejsa eksplicitna, potrebno je prije poziva metode interfejsa izvršiti konverziju objekta u dati interfejs. Konverzija se vrši pomoću operatora konverzije (:>), na način koji je prikazan u sljedećem primjeru:

Anonimna klasa

Programski jezik F# podržava i implementaciju interfejsa kreiranjem anonimne klase iz interfejsa. Tako kreirana anonimna klasa sadrži isključivo metode interfejsa, a za objekat date klase nije potrebno vršiti konverziju u interfejs kako bi se pozivale metode definisane u interfejsu.

Za kreiranje anonimnih klasa koristi se objektni izraz. Objektni izraz za kreiranje anonimne klase bazirane na interfejsu sastoji se od para vitičastih zagrada, u okviru kojih se nalazi operator new, za kojim slijedi naziv interfejsa, a potom i implementacija interfejsa sintaksno identična implementaciji prikazanoj na primjeru definisanja klasa Circle i Square.

Objektni izraz se u praksi najčešće koristi u okviru generatorske funkcije koja prima parametre potrebne za kreiranje objekta date anonimne klase.
Abstraktna klasa

Djelimično implementirana klasa naziva se abstraktna klasa. Abstraktna klasa može da sadrži deklarisane (abstraktne) i implementirane članove. Pošto abstraktni članovi nemaju implementaciju, nemoguće je kreirati objekat za datu abstraktnu klasu. Abstraktna klasa se najčešće koristi kao korjena klasa u hijerarhiji klasa, kada sve klase u hijerarhiji djele određenu zajedničku implementaciju.

Prilikom definisanja abstraktne klase neophodno je markirati datu klasu atributom AbstractClass.

Sljedeći primjer prikazuje definisanje klase Shape koja sadrži abstraktnu metodu Contains i metodu Print koja ima podrazumjevanu implementaciju.

Modul

Modul u F#-u predstavlja način grupisanja funkcija. U C#-u se za tu namjenu koristi statička klasa.

Sljedeći primjer ilustruje definisanje modula Vector koji sadrži funkciju length, kao i način pozivanja navedene funkcije.

Ekvivalentan C# kod glasi:


Advertisements