• 370 likes • 538 Views
F#. Czyli funkcyjny .NET Jakub Rusiłko. Plan prezentacji. Wstęp Co to jest programowanie funkcyjne C# vs F# Cechy języka F# Typy Currying i Partial Function Application OOP w F# Asynchroniczność w F#. Wstęp. Kim jestem? Dlaczego F# i co fajnego jest w programowaniu funkcyjnym?.
E N D
F# Czyli funkcyjny .NET Jakub Rusiłko
Plan prezentacji • Wstęp • Co to jest programowanie funkcyjne • C# vs F# • Cechy języka F# • Typy • Currying i PartialFunction Application • OOP w F# • Asynchroniczność w F#
Wstęp • Kim jestem? • Dlaczego F# i co fajnego jest w programowaniu funkcyjnym?
Zniechęcający kod funkcyjny ((n.lisp_token_pos_guessis to) ((year)) ((p.lisp_token_pos_guessissym) ((pp.lisp_token_pos_guessissym) ((cardinal)) ((lisp_num_digits<4.6)((year))((digits)))) ((lisp_num_digits<4.8) ((name<2880) ((name<1633.2) ((name<1306.4)((cardinal))((year))) ((year))) ((cardinal))) ((cardinal)))))))))
Pogramowanie funkcyjne – kilka definicji • Programowanie funkcyjne (z wikipedii) – filozofia i metodyka programowania będąca odmianą programowania deklaratywnego, w której funkcje należą do wartości podstawowych, a nacisk kładzie się na wartościowanie (często rekurencyjnych) funkcji, a nie na wykonywanie poleceń. • Programowanie funkcyjne jest jak opisywanie twojego zadania matematykowi. Programowanie imperatywne jest jak wydawanie instrukcji idiocie. • Programowanie funkcyjne traktuje wykonanie programu jak ewaluację funkcji matematycznej i stara się unikać stanu oraz zmiennych.
Podział języków funkcyjnych • języki czysto funkcyjne - nie ma zmiennych, nie ma efektów ubocznych, leniwe wartościowanie, we/wy musi się odbywać alternatywnym sposobem, jak na przykład monady (np. Haskell) • języki mieszane - można stosować zmienne, tworzyć efekty uboczne, tradycyjne we/wy, mieszać styl funkcyjny z imperatywnym lub obiektowym, wartościowanie przeważnie zachłanne (np. Lisp, Clojure, Scheme, Erlang, Scala, F#)
Kiedy programowanie funkcyjne może okazać ci się pomocne • Gdy masz trudności z przewidzeniem rezultatu zmian w swoim kodzie z powodu ukrytych zależności i subtelności • Gdy zdajesz sobie sprawę, że ciągle tworzysz te same wzorce i szablony poświęcając mało czasu na kluczowe i interesujące aspekty problemu • Masz trudności z analizą swojego kodu i martwisz się tym, czy dany fragment zostanie wykonany we właściwej kolejności i przy odpowiednich warunkach • Masz trudności z wyrażaniem abstrakcji, która ukrywa JAK kod ma się wykonać, a wyraża tylko CO chcesz osiągnąć • Masz problemy z ogarnięciem kontroli nad kodem asynchronicznym • Gdy kod zachowuje się inaczej na produkcji i inaczej podczas testów jednostkowych
F# - Historia • Początki programowania funkcyjnego to Information Processing Language z 1956, a potem Lisp w 1958 • Języki funkcyjne szybko zostały wyparte przez języki imperatywne jak Fortran (1957) czy COBOL (1959) • W 1973 powstaje język ML. Jest on na tyle dobry, że powstaje wiele języków pochodnych jak Standard ML, Caml i OCaml, który łączy styl funkcyjny z obiektowo zorientowanym stylem imperatywnym • W 2005 powstaje F#, który w dużej mierze jest .NETowąimplemantacjąOCamla.
Cechy języka F# • Statycznie typowany – kompilator zna typy zmiennych i funkcji w momencie kompilacji • Silnie typowany – zmienne nie zmieniają swojego typu • F# nie przeprowadza automatycznego rzutowania typów (tak jak C# czy VB), trzeba rzutować explicite • Zachęca do tworzenia kodu z użyciem zmiennych niemutowalnych, ale pozwala używać zmiennych mutowalnych, jeśli jest to konieczne • Pozwala na korzystanie z bibliotek napisanych w innych językach rodziny .NET i bez problemu się z nimi łączy • Łączy zalety języka funkcyjnego z obiektowym • Zamiast nawiasów klamrowych { i } stosuje wcięcia linii • Wnioskowanie typów (TypeInference) – analogicznie do var w C#
Cechy języka F# • Nie używamy słowa return – zwrot wartości z funkcji jest automatyczny • Unit zamiast void • Automatyczna generalizacja • Kolejność plików w projekcie ma znaczenie
Prosty program w F# openSystem let a =2 Console.WriteLine a
Prosty program w C# using System; namespace ConsoleApplication1 { classProgram { staticint a() { return2; } staticvoidMain(string[] args) { Console.WriteLine(a); } } }
F# Interactive • Interaktywna konsola wspomagająca programowanie • DEMO
Typy w F# • Typy proste (int, char, float, …) • Typy z bibliotek .NET • Typy właściwe dla F#
Tuples (Krotki) • let t1 = (2,3) • lett2 = ("hello",42) • lett3 = (42,true,"hello") • let z =1,true,"hello",3.14// "construct" • let z1,z2,z3,z4 = z // "deconstruct" • let _,z5,_,z6 = z // ignore 1st and 3rd elements • letfirst=fst t1 • letsecond=snd t1
Prosta zamiana miejscami w Krotce (Tuple) w F# vs C# F# C# Tuple<U, T> Swap<T, U>(Tuple<T, U> t) { returnnewTuple<U, T>(t.Item2, t.Item1); } letswap (x,y) = (y,x)
Records (rekordy) • typeComplexNumber= { real: float; imaginary: float } • typeGeoCoord= { lat: float; long: float } • letmyGeoCoord= { lat=1.1; long =2.2 } // "construct" • let { lat=myLat; long=myLong } =myGeoCoord// "deconstruct” • let x =myGeoCoord.lat • let g1 = {lat=1.1; long=2.2} • let g2 = {g1 withlat=99.9} // create a new one
Discriminated Union Type • Typ będący sumą kilku typów typeIntOrBool= | I ofint | B ofbool type Person = {first:string; last:string} // define a record type typeIntOrBool= I ofint | B ofbool typeMixedType= | Tupofint*int// a tuple | P of Person // use the record type defined above | L ofint list // a list of ints | U ofIntOrBool// use the union type defined above
Discriminated Union vs enum oraz PatternMatching • typeSizeUnion= Small | Medium | Large // union • typeColorEnum= Red=0 | Yellow=1 | Blue=2// enum • DEMO
Null i Option type • W czystym F# nie ma pojęcia nulla (istnieje tylko w celu kompatybilności z .net) • Aby oznaczyć brak wartości stosujemy Option Type • Podobne do Nullable w C# z tą różnicą, że Option można użyć z dowolnym typem (również na typach referencyjnych, klasach, itp.) type Option<'a>= | Someof'a | None • DEMO
Units of measure • [<Measure>] type m • [<Measure>] type sec • [<Measure>] type kg • letdistance=1.0<m> • lettime=2.0<sec> • letspeed=2.0<m/sec> • letacceleration=2.0<m/sec^2> • letforce=5.0<kg m/sec^2> • [<Measure>] type N = m/sec^2
Kolekcje - Listy letnumbers = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] letnumbers2=1::2::3::4:: [] letnumbers3 = [1..5] letnumbers4= [1..2..10] letnumbers5 =List.init10 (fun i -> i) • DEMO
Kolekcje - Sekwencje • Są podobne do list z tą różnicą, że ich wartości są wyliczane na bieżąco, gdy są potrzebne (leniwie - LAZY) let sequence1 =seq { 1..10} let sequence2 =seq {10..-1..0} let sequence3 =seq { for a in1..10doyield a, a*a, a*a*a } • DEMO
Niezmienność (Immutability) • Słowo kluczowe letdefiniuje wartość • Value Binding – (wiązanie wartości) pozwala powiązać wartość z symbolem • Niezmienność wymusza inne spojrzenie na problemy • Każda kolejna operacja na zadeklarowanej wartości tworzy nową wartość (nie zmienia starej) – analogia do typu string z C# • Rekurencja zamiast pętli • Niezmienność zachęca do używania pojedynczych wyrażeń zamiast sekwencji poleceń sprawiając, że program jest bardziej deklaratywny Przykład w C#: varres =ImmutableList.Empty<int>().Add(1).Add(3).Add(5).Add(7); //Sytem.Collections.Immutable (.NET 4.5)
Funkcje jako Wartości • Funkcja jest wartością i może być użyta w każdej sytuacji, w której możemy użyć zwykłego int’a czy string’a (First-classfunctions), każda funkcja ma typ (w C# używamy do tego delegatów, w F# typ jest właściwością samej funkcji) • W szczególności funkcja może być parametrem do innej funkcji lub wynikiem wyjściowym funkcji – funkcje wyższego rzędu (Higher-order functions) • DEMO (agregacja)
Sygnatura FUnkcji • int -> int -> int • int -> unit • unit -> string • int -> (unit -> string) • 'a list -> 'a • ('a -> bool) -> 'a list -> 'a list • DEMO
Currying • Ale dlaczego sygnatury funkcji nie rozróżniają między parametrami a typem wyjściowym? • CURRYING– rozbijanie wieloargumentowych funkcji na mniejsze jedno-parametrowe funkcje • HaskellCurry – matematyk, który przyczynił się do rozwoju programowania funkcyjnego • int -> int -> int jest tak naprawdę połączeniem więcej niż jednej funkcji • Nie musimy się tym martwić, kompilator robi to za nas automatycznie • DEMO
Partialfunctionapplication • Dzięki curryingowiwywołanie funkcji z mniejszą ilością parametrów, niż to wynika z definicji funkcji, jest dozwolonym działaniem • Wywołanie funkcji z n-początkowymi parametrami zwróci nową funkcję przyjmującą pozostałe (z oryginalnej funkcji) parametry • Właściwość ta jest jednym z najważniejszych narzędzi programowania funkcyjnego • DEMO
Kilka ciekawych operatorów • |> - forwardpipe operator – przekazuje rezultat operacji po lewej stronie do funkcji po prawej stronie • <| - backwardpipe operator • >> - forwardcomposition operator - złożenie funkcji • << - backwardcomposition operator - złożenie funkcji (w odwrotnej kolejności) • DEMO
Obiektowy F# • Pozwala zaimplementować algorytmy obiektowe 1 do 1 • Ułatwia integrację z .NETem • Dla początkujących może przysłonić korzyści płynące z programowania czysto funkcyjnego • Nie współpracuje dobrze z funkcjami wyższego poziomu oraz z wnioskowaniem typów • DEMO
Object expressions • Pozwala implementować interfejs w locie bez potrzeby tworzenia klasy letmakeResourcename= { newSystem.IDisposable withmemberthis.Dispose() =printfn"%s disposed" name }
AsynchronousWorkflows • DEMO
Messages and Agents • MailboxProcessorimplementuje podejście bazujące na agentach i wiadomościach (kolejki wiadomości) • Działa w osobnym wątku • Pozwala łatwo zarządzać dzielonymi zasobami bez zakleszczeń • Umożliwia łatwe rozdzielenie odpowiedzialności poprzez tworzenie osobnych agentów obsługujących różne rzeczy • DEMO
Quicksort C# publicclassQuickSortHelper { publicstaticList<T> QuickSort<T>(List<T> values) whereT : IComparable { if (values.Count==0) { returnnewList<T>(); } T firstElement=values[0]; varsmallerElements=newList<T>(); varlargerElements=newList<T>(); for (int i =1; i < values.Count; i++) { varelem=values[i]; if (elem.CompareTo(firstElement) <0) { smallerElements.Add(elem); } else {largerElements.Add(elem);} } varresult=newList<T>(); result.AddRange(QuickSort(smallerElements.ToList())); result.Add(firstElement); result.AddRange(QuickSort(largerElements.ToList())); returnresult; } }
Quicksort F# - w stylu funkcyjnym letrecquicksort list = match list with | [] -> [] | firstElem::otherElements-> letsmallerElements=otherElements|>List.filter (fun e -> e <firstElem) |>quicksort letlargerElements=otherElements|>List.filter (fun e -> e >=firstElem) |> quicksort List.concat [smallerElements; [firstElem]; largerElements] letrec quicksort2 =function | [] -> [] | first::rest-> letsmaller,larger=List.partition ((>=) first) rest List.concat [quicksort2 smaller; [first]; quicksort2 larger]
Źródła • http://pl.wikipedia.org/wiki/Programowanie_funkcyjne • http://fsharpforfunandprofit.com/ • http://en.wikibooks.org/wiki/F_Sharp_Programming • Real-World Functional Programming, TomasPetricek i Jon Skeet, Manning Publications, 2010