Instrukcja laboratoryjna z języka F

Transkrypt

Instrukcja laboratoryjna z języka F
Języki programowania na platformie .NET cz.2
2016/17
Instrukcja laboratoryjna cz.5
Język funkcyjny F#
Prowadzący: Tomasz Goluch
Wersja: 3.0
I.
Słowa kluczowe: „let”, „fun” i operator „|>” w F#1
Cel: Zapoznanie z najważniejszymi słowami kluczowymi języka F#.
Uruchom Visual Studio 2013 i utwórz nowy Library projekt – MyProject (Visual F#).
W pliku Script.fsx wpisz polecenie:


#light – deklaruje użycie składni lekkiej (lightweight syntax). Dostępna jest
jeszcze składnia pełna (verbose syntax), która zawsze jest obsługiwana. W celu
zapewnienia kompatybilności z rodziną funkcyjnych języków programowania ML
(ang. Meta Language)2 można wyłączyć składne lekką poprzez zastosowanie
dyrektywy #light "off".
let y = 0 – wiąże identyfikator y z wartością 0. Słowo kluczowe let jest jednym
z najważniejszych w F#, pozwala również na wiązanie identyfikatora z nazwą
funkcji3.
W celu aktywacji polecenia należy zaznaczyć interesujące nas linie skryptu i z menu
kontekstowego wybrać: Execute In Interactive albo (Alt + Enter).
Wynik: val y : int = 0 powinien pojawić się w oknie F# Interactive4. Informuje on o
typie wiązanej wartości y – value, inteeger.
1
Instrukcja przygotowana na podstawie wykładu Luca Bolognese „An Introduction To Microsoft F#”:
http://channel9.msdn.com/Blogs/pdc2008/TL11
2
Więcej informacji na: http://msdn.microsoft.com/en-us/library/dd233199.aspx.
3
Więcej informacji na: http://msdn.microsoft.com/en-us/library/dd233238.aspx
4
Okno F# Interactive można uruchomić wybierając: VIEW → Other Windows → F# Interactive (Ctrl + Alt + F).
1
Wiązanie z listą float’ów:
Wiązanie funkcji:
Wywołanie funkcji sqr z argumentem 3 (wymagane dwa średniki na końcu):
> sqr 3;;
val it : int = 9
Funkcja licząca sumę kwadratów (styl imperatywny, iteracyjny):


mutable – tworzy zmienną której wartość może ulec zmianie (tak jak w językach
imperatywnych). W czystym programowaniu funkcjonalnym zmienne w ogóle nie
powinny być używane5.
<- – pozwala na przypisanie nowej wartości do zmiennej.
F# jest językiem wrażliwym na dzielenie linii oraz wcięcia. Instrukcje wykonywane wewnątrz
funkcji bądź pętli – tak jak ma to miejsce w powyższym przykładzie – muszą być wcięte
głębiej (rozpoczynać się bardziej od prawej strony). W momencie kiedy wymagane jest
wcięcie musi ono zawierać przynajmniej jedną spację – nie można używać tabulacji6. Jednak
jest to dopuszczalne przy domyślnych ustawieniach edytora VS, ponieważ tabulacja
zamieniana jest na 4 znaki spacji (TOOLS → Options → Text Editor → F# → Tabs).
Ponownie zaimplementujmy funkcję licząca sumę kwadratów ale tym razem funkcjonalnie:


rec – funkcja może działać rekurencyjnie7.
match, with, |, -> – wyrażenie znajdujące się po match jest porównywane ze
wzorcami w kolejnych gałęziach rozpoczynających się od |. Pierwsze dopasowanie
powoduje przerwanie porównywania i wykonanie wyrażenia zamieszczonego po ->8
5
Więcej informacji na: http://msdn.microsoft.com/pl-pl/library/dd233185.aspx
Więcej informacji na: https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/codeformatting-guidelines
7
Więcej informacji na: http://msdn.microsoft.com/en-us/library/dd233232.aspx
6
2

:: – podział listy na pierwszy element h (głowę) i resztę elementów t (ogon). Pustą
listę oznaczamy za pomocą dwóch nawiasów kwadratowych [].
Jeszcze raz, ale tym razem funkcjonalnie i bez rekurencji:


Seq.map – funkcja zwraca nową kolekcję składającą się z wyników zastosowania
funkcji mapującej podanej jako pierwszy parametr (w powyższym przykładzie sqr)
na elementach kolekcji wejściowej przekazanej jako drugi parametr (w powyższym
przykładzie lista nums przekierowana przez |> ) 9.
Seq.sum – sumuje wszystkie elementy kolekcji przekazanej jako parametr10.
W celu poprawienia czytelności zagnieżdżonych funkcji możemy uszeregować w kolejności
wykonania:

|> – operator przekierowania, pozwala na użycie, wartości otrzymanych po lewej
stronie, w wyrażeniu po prawej stronie tego operatora.
Informacje na temat wszystkich słów kluczowych można znaleźć na:
http://msdn.microsoft.com/pl-pl/library/dd233249.aspx, a opisujące pozostałe
funkcje modułu Collections.Seq tutaj: http://msdn.microsoft.com/plpl/library/ee353635.aspx.
W celu zrównoleglenia wcześniejszego kodu należy doinstalować bibliotekę
FSharp.Collections.ParallelSeq, najlepiej za pomocą narzędzie NuGet. W oknie Package
Manager Console11 uruchamiamy polecenie: Install-Package
FSharp.Collections.ParallelSeq.
8
Więcej informacji na: http://msdn.microsoft.com/en-us/library/dd233242.aspx
Więcej informacji na: http://msdn.microsoft.com/pl-pl/library/ee370346.aspx
10
Więcej informacji na: http://msdn.microsoft.com/pl-pl/library/ee370214.aspx
11
Jeśli okno nie jest dostępne (domyślnie znajduje się ono w tym samym miejscu co F# Interactive, poniżej okna
edytora – jedna z dostępnych zakładek) to możemy je znaleźć w: TOOLS → NuGet Package Manager →
Package Manager Console.
9
3
To samo co dotychczas ale równolegle:
Przewagą F# nad innymi językami funkcyjnymi jest fakt, że możemy wykorzystać całe
bogactwo, które oferuje nam środowisko .NET. Oto przykład pobrania historycznych
informacji finansowych o firmie Microsoft (MSFT) z witryny Yahoo Finance:
#light
open System.Net
open System.IO
let ticker = "MSFT"
let url = "http://real-chart.finance.yahoo.com/table.csv?s=" + ticker +
"&d=10&e=12&f=2014&g=d&a=2&b=13&c=1986&ignore=.csv"
let req = WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let csv = reader.ReadToEnd()
Pobrany plik na następującą zawartość:
Date,Open,High,Low,Close,Volume,Adj Close
2014-11-12,48.56,48.92,48.52,48.78,22722000,48.78
2014-11-11,48.85,48.95,48.65,48.87,22233600,48.87
2014-11-10,48.65,49.15,48.55,48.89,36365400,48.89
…
i zawiera kilka tys. linii danych rozdzielonych przecinkiem. W pierwszym kroku musimy
usunąć nagłówek, następnie dzielimy dane. Ustalamy, że długość sekwencji równa jest 7 i
wybieramy pierwszą i ostatnią pozycję: datę i skorygowaną cenę zamknięcia.
let prices =
csv.Split([|'\n'|])
|> Seq.skip 1
|> Seq.map (fun line -> line.Split([|','|]))
|> Seq.filter(fun values -> values |> Seq.length = 7)
|> Seq.map (fun values ->
System.DateTime.Parse(values.[0]),
float values.[6])
Dane zwracane przez funkcję prices możemy zaprezentować w bardziej przyjaznej formie np.
wyświetlić w formie wykresu. W tym celu musimy doinstalować pakiet FSharp.Charting,
polecenie: Install-Package FSharp.Charting. Oraz uruchomić poniższy skrypt:
#load @"..\packages\FSharp.Charting.0.90.14\FSharp.Charting.fsx"
open FSharp.Charting
4
Chart.Line(prices).ShowChart()
lub:
#r @"..\packages\FSharp.Charting.0.90.14\lib\net40\FSharp.Charting.dll"
#r "System.Windows.Forms.DataVisualization.dll"
open FSharp.Charting
Chart.Line(prices).ShowChart()
Jeśli chcemy utworzyć z powyższego kodu funkcję wystarczy podać jej nazwę
(loadPrices) i listę parametrów (w naszym przypadku 1 parametr – ticker), ciało funkcji
należy wyekstrahować tabulatorami i podać zwracany typ (prices):
let internal loadPrices ticker =
let url = …
(…)
let prices =
(…)
prices
Proszę zauważyć, że ticker jest przyjmowany jako parametr zatem nie należy go redefiniować
w kodzie. Wywołanie funkcji jest następujące:



II.
Chart.Line(loadPrices "MSFT").ShowChart();;
Chart.Line(loadPrices "ORCL").ShowChart();;
Chart.Line(loadPrices "EBAY").ShowChart();;
– historia cen akcji dla firmy Microsoft
– historia cen akcji dla firmy Oracle
– historia cen akcji dla firmy Ebay
F# w ujęciu obiektowym
Cel: Zapoznanie z podstawami OOP w F#.
W F# możemy jak w normalnym języku zorientowanym obiektowo definiować klasy.
Przykład klasy analizatora giełdowego wyliczającego odchylenie standardowe i stopę zwrotu
w zadanym okresie. Jako parametry do funkcji GetAnalysers przekazujemy ticker spółki
giełdowej i interesujący na okres. Kod należy umieścić w pliku .fs wraz z funkcją prices i
kodem pobierający historyczne dane. Plik z rozszerzeniem .fs, reprezentuje kod źródłowy
będący częścią biblioteki , w przeciwieństwie do dotychczas wykorzystywanego pliku .fsx
który zawiera skrypty. Przykład:
module MyProject
type public StockAnalyzer (lprices, days) =
let prices =
lprices
|> Seq.map snd
|> Seq.take days
static member public GetAnalysers (tickers, days) =
tickers
|> Seq.map loadPrices
|> Seq.map (fun prices -> new StockAnalyzer(prices, days))
member s.Return =
let lastPrice = prices |> Seq.nth 0
let startPrice = prices |> Seq.nth (days - 1)
5
lastPrice / startPrice - 1.
member s.StdDev =
let logRets =
prices
|> Seq.pairwise
|> Seq.map (fun(x, y) -> log (x / y))
let mean = logRets |> Seq.average
let sqr x = x * x
let var = logRets |> Seq.averageBy (fun r -> sqr (r - mean))
sqrt var
Proszę wykorzystać F# klasę w projekcie VB Console Application i wyświetlić informacje
(odchylenie standardowe i stopa zwrotu) dla firm o nast. tickerze: "MSFT" – Microsoft,
"ORCL" – Oracle, "EBAY" – Ebay.
Kolejnym krokiem jest zrównoleglenie wykonywanych operacji. W pierwszym kroku należy
poinformować kompilator, że w metodzie loadPrices będziemy wykonywać pewne
operacje asynchronicznie (pobieranie danych z internetu). Służy do tego słowo kluczowe
Async (musimy zdefiniować dane zwracane przez funkcję przy pomocy Return):
let internal loadPrices ticker = async {
(…)
let! resp = req.AsyncGetResponse()
let stream = resp.GetResponseStream()
let reader = new AsyncStreamReader(stream)
let! csv = reader.ReadToEnd()
(…)
return prices }
Wewnątrz metody możemy uruchomić asynchroniczną wersję funkcji GetResponse (uwaga
na ! przy słowie kluczowym let). Pozwoli ona na zwolnienie wątku i powrót do obliczeń po
pojawieniu się wymaganych danych. Klasa AsyncStreamReader wymaga zainstalowania
pakietu FsPowerPack.Community (Nuget instrukcja: Install-Package
FsPowerPack.Community)
W ostatnim kroku wywołujemy równolegle naszą metodę anonimową dla danych zwróconych
przez loadPrices.
static member public GetAnalysers (tickers, days) =
tickers
|> Seq.map loadPrices
|> Async.Parallel
|> Async.RunSynchronously
|> Seq.map (fun prices -> new StockAnalyzer(prices, days))
Proszę ponownie uruchomić program i porównać działanie (całkowity czas wykonania oraz
pomiędzy kolejnymi wynikami).
6
III.
Zadanie domowe
Cel: Pogłębienie wiedzy na temat F#.
Z poniższej listy problemów: https://projecteuler.net/ należy wybrać przynajmniej dwa tematy
i wysłać ich propozycję na adres: [email protected] – temat wybrany i rezerwowy (ew.
kilka). Założenie jest takie, że docelowe tematy nie powinny się one powtarzać wśród
studentów. Po zatwierdzeniu przez prowadzącego proszę o przesłanie kodów rozwiązań na
ten sam adres.
7