Funkcionální programování v praxi Tomáš Petříček http://tomasp.net/blog tomas@tomasp.net
Funkcionální programování v praxi» Možná více, než si myslíte Lze o tom napsat (a prodávat) knihy» V ne-funkcionálních jazycích Microsoft C# 3.0 a projekt LINQ Parallel Extensions for.net Některé vlasntosti v Pythonu Nový standard C++ 0x» Moderní funckionální jazyky Clojure LISP-style jazyk pro JVM Scala kombinace objektů a funkcionálního programování F# Jazyk pro.net, vychází z ML respektive OCaml
Microsoft Visual F#» Programovací jazyk ve Visual Studiu 2010 Autorem je Don Syme z Microsoft Research, Cambridge Funkcionální aspekty Programujeme skládáním výrazu Vypočítáváme výsledek programu (jako matematika) Objektově orientované aspekty Plná podpora pro objektový model.net jazyků Lze používat.net z F# a naopak F# z.netu Podporuje interaktivní programování Interaktivní testování kódu při jeho psaní Začneme jednoduše, postupně upravujeme Umožňuje některé imperativní konstrukce Užitečné pro optimalizace a pro interoperability
Rychlý přehled jazyka F# a používání.net knihoven z F#
Deklarace hodnot pomocí let» Immutable hodnota se nemění let num = 10 num = 11 Returns false» Type inference typy známé za překladu! let str = "Hello world!" let stock = ("MSFT", 25) let nums = [1; 2; 3; 4] Value: string» Unified funkce jsou také hodnoty Tuple: string * int List: int list let twotimes num = num * 2 Function type: int -> int
Funkce, rekurze a abstrakce» Vytváříme nové hodnoty, místo modifikace Cykly lze zapsat pomocí rekurzivních volání» Nechceme opakovat podobné rekurizvní vzory Rekurzivní kód můžeme parametrizovat Higher-order functions berou funkci jako parametr» Mnoho takových funkcí už je k dipozici Například pro práci se základními datovými typy Nejčastěji je píšeme při deklaraci vlastního typu
Práce s datovým typem záznam» Deklarace typu určuje (immutable) položky type Rect = { Left : float32 Top : float32 Width : float32 Height : float32 }» Funkce pro práci se záznamem Může například vytvořit nový seznam a vrátit jej let deflate rc wspace hspace = { Top = rc.top + wspace Left = rc.left + hspace Width = rc.width - (2.0f * wspace) Height = rc.height - (2.0f * hspace) }
Objektově orientované aspekty» OOP je způsob organizace kódu, nic jiného! Připojení související funkcionality k typu/objektu Pojmenovává this type Rect = { Left : float32 Top : float32 Width : float32 Height : float32 } Obvyklá deklarace typu v F# K typu připojujeme deklarace členů member x.deflate(wspace, hspace) = { Top = x.top + wspace Left = x.left + hspace Width = x.width - (2.0f * wspace) Height = x.height - (2.0f * hspace) } Zkompiluje se jako normální.net metoda Funkcionální chování!
Používání.NET typů» Lze používat všechny typy, bez omezení Není třeba žádný explicitní import F# je.net jazyk Operátor <- přiřazuje hodnotu vlastnosti Operátor. přistupuje ke členům objektu Jeden zápis main fce. open System open System.Windows.Forms.NET atributy [<STAThread>] do let form = new Form(Width = 400, Height = 300) form.text <- "Hello World Form Application.Run(form) Import.NET namespaces Zadání hodnoty vlastnosti již při vytváření objektu Modifikace vlastnosti
Demo 3D fraktál pomocí WPF
Reaktivní programování v F#
Práce s událostmi» Událost může se stát, nese nějaký argument MouseMove při pohybu myší, obsahuje X a Y kurzoru» Běžný způsob práce s událostmi v.net Pomocí registrace callback funkce Je zavolána systémem v případě události let w1 = new FileSystemWatcher ("C:\\Temp", EnableRaisingEvents = true) w1.renamed.add(fun fse -> Callback funkce if isnothidden(fse) then printfn "%s renamed to %s" fse.oldfullpath fse.fullpath)
Práce s událostmi, lépe» Kdybychom měli seznam objektů, místo události: Vytváříme seznam let infos = renamedinfos > List.filter isnothidden > List.map formatfileevent Pouze viditelné soubory Místo informací o souboru vracíme řetězec» Podobné funkce existují i pro události! Vytváříme novou událost let renamedinfoevent = w.renamed > Observable.filter isnothidden > Observable.map formatfileevent Deklarativně popíšeme jaké události nás zajímají Imperativní callback až na koncovou událost
Demo Počítání kliknutí na tlačítka Increment Decrement map (always 1) map (always -1) merge scan (+) 0 listen ( )
Computation expressions» Skládání výpočtů uživatelsky-definovaným způsobem <builder> { let! arg = function1() let! res = function2(arg) return res }» Význam je specifikovaný objektem <builder> Například, můžeme automaticky propagovat null hodnoty (neboli Maybe monad v jazyce Haskell) let LoadFirstOrder(customerId) = nullable { let! customer = LoadCustomer(customerId) let! order = customer.orders.firstordefault() return order }
Asynchronous workflows» Použití předcházejícího zápisu pro psaní dlouhoběžících operací které neblokují vlákna let http(url:string) = async { let req = HttpWebRequest.Create(url) let! rsp = req.asyncgetresponse() let reader = new StreamReader(rsp.GetResponseStream()) return! reader.asyncreadtoend() } let pages = Async.Parallel [ http(url1); http(url2) ]» Vhodné pro různé způsoby paralellního programování Fork/Join paralelismus s I/O operacemi Aktivní objekty komunikující pomocí zpráv
Počítání kliknutí na formulář» Progam zapisujeme pomocí agentů Agent většinu času pouze čeká, pak rychle reaguje Bere parametr int a vytváři objekt Async<unit> Výpočet bude pokračovat po kliknutí let rec loop(count) = async { let! me = Reactive.AwaitEvent(lbl.MouseDown) let add = if me.button = MouseButtons.Left then 1 else 0 lbl.text <- sprintf "Clicks: %d" (count + add) return! loop(count + add) } Rekurzivní volání loop(0) > Async.Start» To vypadá podobně jako Observable.scan Ne vždy máme ale tak jednoduché chování...
Počítání kliknutí na formulář» Progam zapisujeme pomocí agentů Agent většinu času pouze čeká, pak rychle reaguje let rec loop(count) = async { let! me = Reactive.AwaitEvent(lbl.MouseDown) let add = if me.button = MouseButtons.Left then 1 else 0 lbl.text <- sprintf "Clicks: %d" (count + add) let! _ = Reactive.Sleep(1000) return! loop(count + add) } loop(0) > Async.Start Výpočet bude pokračovat za 1000ms» Toto již pomocí Observable.scan (snadno) nejde
Demo Aplikace na kreslení obdélníků Starting Initial state Waiting MouseMove (button released) MouseDown Drawing MouseMove (with button pushed)
Reprezentace dat pomocí F# typů
Datové typy v F#» Vytváříme složitější typy z jednoduchých Založené na principu compositionality 1) Několik základních typů pro začátek 2) Několik způsobů pro konstrukci typů» Základní typy: int, string, float,» Typové konstruktory: _ * _ Vytváří n-tici (na příklad string * int) list<_> Vytváří seznam (na příklad list<int>)» Typove konstruktory lze libovolně vnořovat Na příklad list<int * string> nebo string * list<int>
Demo Popisování finančních kontraktů pomocí datových typů v F#
Díky za pozornost!?» Mail: tomas@tomasp.net» Blog: http://tomasp.net/blog