Wprowadzenie do F#

W tym roku postanowiłem zacząć spotkania Grupy .NET MIMUW od wprowadzenia do języka F#. Poniżej znajdziecie krótki wstęp, link do prezentacji oraz nagranie ze spotkania.

Wprowadzenie

F# to functional-first programming language, czyli przede wszystkim funkcyjny język programowania, które również zawiera elementy innych paradygmatów, jak chociażby programowanie obiektowe, które jest cechą platformy .NET

Na czym polega programowanie funkcyjne?

Głównie na innym podejściu do rozwiązywania problemów. Częściej niż zmiennymi, będziemy się posługiwali stałymi oraz ograniczali skutki uboczne funkcji (zmiany stanu), co pozwala na bezproblemowe programowanie współbieżne.

Do tego funkcje są obywatelami pierwszej kategorii i można je przekazywać w argumentach innym funkcjom albo zwracać. Ogólnie będzie tu bardziej matematyczne podejście do rozwiązywania problemów.

Nie będziemy używali pętli takich jakie znamy z języków imperatywnych, a zastąpimy je wysoce wydajnymi funkcjami z rekurencją ogonową. Wsparcie dla takich operacji jest również wbudowane w IL (zobacz Tail calls in F#).

Definiowanie obiektów

Zacznijmy od tego jak definiuje się różne obiekty. Mamy do tego jedno słówko let (de facto jest ich więcej, ale let jest najważniejszy).

let a = 1               // stała typu int
let s = "Hello World"   // stała typu string
let mutable zmienna = 2;// zmienna typu int
let funkcja i = i+1     // funkcja typu int->int

Jak widać zmienne potrzebują dodatkowego słowa mutable (modyfikowalny) w deklaracji. Funkcje natomiast deklaruje się tak jak stałe, ale mają parametry. Typ funkcji zawiera ->, które pokazują kolejne argumenty przekazywane do funkcji, a na końcu zwracaną wartość. Takie coś pozwala na częściowe aplikowanie funkcji, np:

let skomplikowanaFunkcja a b c d = (a + 2 * b) ** (c/d)

let sF57 = skomplikowanaFunkcja 5 7

Najpierw zadeklarowaliśmy skomplikowanaFunkcja z czterema parametrami, a następnie przypisujemy do sF57 tę funkcję tylko z dwoma parametrami. Co się dzieje? sF57 staje się funkcją przyjmującą dwa parametry (c i d).

Typowanie

Jak pewnie zauważyliście, powyżej nie ma w definicjach żadnych typów. Ale w przeciwieństwie do języków dynamicznie typowanych, F# jest silnie typowany, mając jednocześnie silnik wykrywający typy. Silne typowanie przejawia się tutaj m.in. tym, że nie ma domyślnych castów. Jeśli funkcja prosi o float to musi go dostać, a nie np. int.

Jeśli chcemy być mądrzejsi od kompilatora, lub piszemy coś tak zawiłego, że nie umie się domyśleć, to możemy samemu zadeklarować typ:

let f (x:int) (y:string) = //...
Warunki

Aby kontrolować przebieg funkcji, potrzebujemy często wyrażeń warunkowych. Ponieważ funkcja zawsze musi coś zwracać, to rzadko będziemy spotykali if bez else.

let sign x =
    if x > 0 then 1
    else if x < 0 then -1
    else 0

Jednocześnie zwróćcie uwagę na wcięcia. Tak jak w Pythonie, wcięcia pokazują gdzie zaczyna i kończy się ciało bloku.

Kolejna uwaga, nie ma słówka return. Zamiast tego zwracana jest wartość ostatniego wyrażenia w funkcji.

Krotki

Czasem dojdziemy do wniosku, że fajnie byłoby zwrócić kilka wartości na raz. Krotki właśnie do tego służą. Typ krotki to typy jej poszczególnych elementów rozdzielone *

let tuple = (1, "text", 12.0)   // int * string * float

Dla krotek dwuelementowych mamy zadeklarowane dwie funkcje: fst i snd, które zwracają wartość na odpowiednio pierwszej i drugiej pozycji w krotce.

Listy

Podstawowym mechanizmem przechowywania danych o zmiennej długości są listy. Listę możemy zadeklarować na kilka sposobów:

let lista = [1; 2; 3; 4; 5]
let lista = [1..5]
let lista = [for i in 1..5 -> i]
let lista = [for i in 1..5 do yield i]

Lista ma postać łańcucha: mamy wskaźnik na głowę - pierwszy element oraz na ogon - listę zawierającą pozostałe elementy. Możemy zwiększać listy na dwa sposoby: przez dołączanie kolejnych głów, przez łączenie list:

let lista2 = 0 :: [1;2;3]   // [0;1;2;3]
let lista2 = [0;1] @ [2;3]  // [0;1;2;3]

Aby następnie odwołać się do głowy listy możemy posłużyć się tym, że jest to również obiekt i mam własność Head

let pierwszy = lista2.Head  // 0

Więcej fajnych rzeczy osiągniemy przez funkcje zapisane w module List. Wiele z nich będzie funkcjami wyższych rzędów, czyli takimi które przyjmują lub zwracają inne funkcje.

List.map (fun x -> x*x) [1..5]          // [1;4;9;16;25]
List.filter (fun x -> x % 2) [1..10]    // [2;4;6;8;10]
List.sum [1..10]                        // 55

Wideo

Dalszy ciąg wprowadzenia do F# możecie obejrzeć na poniższym wideo. Niestety przez zapomnienie dopiero po 20 minutach prezentacji zacząłem nagrywać, ale to czego nie ma w wideo, rozpisałem powyżej.

Podczas prezentacji korzystam na Linuxie Mint z edytora VS Code oraz rozszerzenia Ionide. Do tego mam zainstalowany pakiet fsharp, a na Windowsie wszystkie te rzeczy są dostępne w Visual Studio po zainstalowaniu “F# Tools for Visual Studio”.

Prezentacja

Prezentacja jest w nietypowej formie, w pliku fsx, czyli F# Script. Pozwala to na uruchamianie kawałków kodu na bieżąco, aby zobaczyć co jak działa.

Prezentację znajdziecie tutaj.

Więcej

Zachęcam do odwiedzenia strony Fundacji F#, na której znajdziecie mnóstwo przydatnych linków i informacji.

Polecam również bardzo fajne wprowadzenie do F# z Sacha’s Blog Of Programmaticalness.