ROZDZIAŁ 1 . Nadawanie wartości i komunikacja z użytkownikiem
W rozdziale tym omówię, w jaki sposób nadajemy wartości w języku F#. Przedstawię również sposoby komunikowania się z użytkownikiem.
1.1.Programowanie funkcyjne - informacje ogólne
Języki programowania, takie jak np. C#, C/C++, Java, Pascal, są nazywane imperatywnymi językami programowania, ponieważ zawierają sekwencje zadań do wykonania. Mechanizm programowania imperatywnego jest następujący: programista jawnie, krok po kroku, definiuje, jakie instrukcje należy wykonać na danych programu, aby otrzymać pożądany efekt.
Programowanie funkcyjne działa inaczej. Zamiast wykonywać zadania sekwencyjnie, języki funkcyjne wyznaczają jedynie wartości poszczególnych wyrażeń. Mechanizm tego programowania wygląda następująco: programista składa odpowiednie wyrażenia w celu uzyskania korzystnego wyniku.
Programy funkcyjne składają się jedynie z funkcji, które są podstawowymi elementami tego języka. Główny program jest funkcją, do której podajemy argumenty, a w zamian otrzymujemy wyznaczoną wartość - wynik działania programu. Główna funkcja składa się z innych funkcji, które z kolei składają się z jeszcze innych funkcji. Funkcje takie dokładnie odpowiadają funkcjom w czysto matematycznym znaczeniu - przyjmują pewną liczbę parametrów i zwracają wynik. Każda operacja wykonywana podczas działania funkcji, ale niemająca związku z wartością zwracaną przez funkcję to tzw. efekt uboczny (np. operacje wejścia-wyjścia, modyfikowanie zmiennych globalnych).
Funkcje, które nie mają efektów ubocznych, nazywane są funkcjami czystymi (pure function).
Język F# jest:
- wieloparadygmatowym językiem programowania zawierającym w sobie głównie cechy języka funkcyjnego, ale umożliwiającym także pisanie kodu imperatywnego oraz obiektowego;
- językiem silnie typowanym5 zaprojektowanym w celu pisania prostego, solidnego i wydajnego kodu, służącego do rozwiązywania złożonych problemów informatycznych;
- nowoczesnym językiem programowania typu open source;
- częścią platformy .NET firmy Microsoft;
- wzorowany na językach OCaml i Haskell6;
- językiem, który łączy w sobie różne cechy, takie jak nowoczesność, zwięzłość, wydajność i ekspresywność [3].
W języku F# wszystko jest wartością, w przeciwieństwie do języków obiektowych, w przypadku których wszystko jest obiektem (zob. rozdział 5).
Programiści języka F# rezerwują określenie "obiekt" dla wartości specjalnego rodzaju:
- wartości, których obserwowalne właściwości zmieniają się w trakcie wykonywania programu;
- wartości, które wskazują dane lub stany określające tożsamość (takie jak unikatowe identyfikatory całkowitoliczbowe) albo które wyznaczają ogólną tożsamość obiektu, służącą do odróżniania obiektów od innych wartości identycznych w pozostałych aspektach;
- wartości, które można sprawdzić, by ustalić dodatkowe mechanizmy za pomocą rzutowania, konwersji i interfejsów [3].
Szanowny Czytelniku, jeżeli do tej pory sądziłeś, że programowanie imperatywne i obiektowe to jedyny sposób myślenia o programowaniu, to pisanie programów w języku F# może wymagać od Ciebie zupełnie innego podejścia.
1.2.Identyfikator wartości
Do deklarowania identyfikatorów w języku F# używamy słowa kluczowego let7. Jest to najważniejsza instrukcja, jaką będziemy stosowali, programując w tym języku. Służy ono do definiowania danych, obliczanych wartości i funkcji. Po lewej stronie tej instrukcji często znajduje się prosty identyfikator, można tam jednak umieszczać także wzorce lub nazwę funkcji z listą nazw argumentów. Po prawej stronie instrukcji let, czyli po znaku =, znajduje się wartość lub wyrażenie, np.
let ą = 2 // Identyfikatorowi ą nadajemy wartość 2
Oznacza to, że do zadeklarowania identyfikatora o nazwie ą użyliśmy słowa kluczowego let i nadaliśmy mu wartość równą 2.
UWAGA! Język programowania Visual F# zawiera w sobie standard Unicode, co oznacza, że identyfikatory wartości zawierają również polskie znaki.
W innych językach (np. C++, Java, C#) wartość lokalna (local value) jest nazywana zmienną lokalną (local variable). Programiści języka F# wolą posługiwać się pojęciem wartość, zamiast zmienna.
W języku F# nie można modyfikować wartości lokalnych po ich zainicjowaniu, chyba że dana wartość lokalna jest jawnie opatrzona modyfikatorem mutable (zob. rozdział 4). W języku F# istnieją wartości (dane) całkowicie niemodyfikowalne (immutable). Oznacza to, że ani określona wartość lokalna, ani dane, które ona wskazuje, nie mogą zostać zmienione za pomocą zewnętrznych modyfikacji. Takie dane nazywane są wartościami niemodyfikowalnymi. Na przykład wszystkie podstawowe typy8 (basic types) z platformy .NET9 (m.in. liczby całkowite, łańcuchy znaków i wartości typu System.DateTime) są niemodyfikowalne.
Oto prosty przykład zadania demonstrującego zastosowanie słowa kluczowego let w programie.
Zadanie 1.1
Napisz program, który ilustruje proste zastosowanie słowa kluczowego let. Wynik działania programu wyprowadź na ekran komputera.
Przykładowe rozwiązanie - listing 1.1
// Zadanie 1.1
open System
Console.WriteLine("Prosty program ilustrujący wykorzystanie słowa kluczowego let.")
let ą = 2 // Identyfikatorowi ą nadajemy stałą wartość 2
let b = 3 // Identyfikatorowi b nadajemy stałą wartość 3
let ć = ą + b // Identyfikator ć oblicza sumę dwóch identyfikatorów ą + b
Console.Write("ą = {0}, ", ą) // Wyświetlenie wartości identyfikatora ą
Console.Write("b = {0}, ", b) // Wyświetlenie wartości identyfikatora b
Console.Write("ć = ą + b = {0}.", ć) // Wyświetlenie wartości identyfikatora ć
Console.WriteLine(); // Wyświetlenie pustej linii
// Zatrzymanie konsoli
Console.WriteLine("Naciśnij dowolny klawisz.")
Console.ReadKey(true) |> ignore
Wcięcie w programie ilustrowane linijką kodu...
let ć =
ą + b // Identyfikator ć oblicza sumę dwóch identyfikatorów ą + b
...pełni w języku F# rolę nawiasów klamrowych {}, tak jak to można spotkać np. w języku C++.
Wcięcie to jest obowiązkowe. Brak jego jest sygnalizowane przez kompilator.
Wcięcie kończy się w miejscu, gdzie kolejny wiersz nie jest już przesunięty.
Linijka kodu...
open System
...informuje nas, że do programu została dołączona przestrzeń nazw10 System. Pozwoli to nam m.in. na skorzystanie z konsolowych operacji wejścia-wyjścia (tj. np. metod klasy Console) na platformie .NET.
Następna linijka...
Console.WriteLine("Prosty program ilustrujący wykorzystanie słowa kluczowego let.")
...wyświetli na ekranie komputera następujący tekst:
Prosty program ilustrujący wykorzystanie słowa kluczowego let.
W kolejnych linijkach kodu...
let ą = 2 // Identyfikatorowi ą nadajemy wartość 2
let b = 3 // Identyfikatorowi b nadajemy wartość 3
...zostaną nadane określone wartości poszczególnym identyfikatorom: ą = 2 i b = 3.
Większość czytelników, programujących w językach imperatywnych, takich jak C/C++, Java itd., może taki zapis traktować jako przypisanie do zmiennej. Istnieje tutaj pewne podobieństwo, ale w czystych funkcjonalnych językach nadana raz wartość identyfikatorowi nie może ulec zmianie w trakcie działania programu. Dlatego będziemy używali określenia identyfikator wartości, zamiast zmienna.
Linijka kodu:
Console.Write("ć = ą + b = {0}.", ć) // Wyświetlenie wartości identyfikatora ć
gdzie:
ć = ą + b - oblicza i wyświetla na ekranie monitora wartość tego identyfikatora;
{0} - oznaczona tak sekwencja wyznacza miejsce na dane.
Linijka kodu...
Console.ReadKey(true) |> ignore
...wstrzymuje zamknięcie konsoli do momentu naciśnięcia dowolnego klawisza. Operator |> to tzw. pipe-forward operator (zob. rozdział 2), który nakazuje zignorowanie zwracanego wyniku.
Komentarze w języku F# oznaczamy dwoma ukośnikami //. Służą one do opisu linijek programu i nie są kompilowane.
Rezultat działania programu można zobaczyć na rysunku 1.1.
Prosty program ilustrujący wykorzystanie słowa kluczowego let.
ą = 2, b = 3, ć = ą + b = 5.
Naciśnij dowolny klawisz.
Rysunek 1.1. Efekt działania programu Zadanie 1.1
UWAGA! Napis Naciśnij dowolny klawisz będzie pojawiał się w dalszej części książki tylko w listingach programów.
Program z zadania 1.1 można również przedstawić w innej formie:
open System
let main() =
Console.WriteLine("Prosty program ilustrujący wykorzystanie słowa kluczowego let.")
let ą = 2 // Identyfikatorowi ą nadajemy stałą wartość 2
let b = 3 // Identyfikatorowi b nadajemy stałą wartość 3
let ć = ą + b // Identyfikator ć oblicza sumę dwóch identyfikatorów a + b
Console.Write("ą = {0}, ", ą) // Wyświetlenie wartości identyfikatora ą
Console.Write("b = {0}, ", b) // Wyświetlenie wartości identyfikatora b
Console.Write("ć = ą + b = {0}.", ć) // Wyświetlenie wartości identyfikatora ć
Console.WriteLine(); // Wyświetlenie pustej linii
main()
// Zatrzymanie konsoli
Console.WriteLine("Naciśnij dowolny klawisz.")
Console.ReadKey(true) |> ignore
Skorzystaliśmy tutaj z funkcji main(), odgrywającej rolę funkcji głównej.
W dalszej części tej książki nie będziemy korzystali z funkcji głównej main(), zachowując strukturę programu, jak w zadaniu 1.1.
Nie jest to do końca poprawne, ale... program się kompiluje!
W ramach ćwiczeń utrwalających wymagający czytelnicy mogą przepisać wszystkie listingi programów zawartych w tej książce do wspomnianej postaci, wykorzystując funkcję główną main() oraz punkt wejścia do programu [<EntryPoint>] (zob. Od Autora).
Zadanie 1.2
Napisz program, który wyświetla na ekranie komputera wartość predefiniowanej stałej ? = 3,14... Należy przyjąć format wyświetlania tej stałej z dokładnością do pięciu miejsc po przecinku.
Przykładowe rozwiązanie - listing 1.2
// Zadanie 1.2
open System
Console.WriteLine("Program wyświetla stałą pi z zadaną dokładnością.")
Console.WriteLine("Pi = {0:##.#####}.", Math.PI)
// Zatrzymanie konsoli
Console.WriteLine("Naciśnij dowolny klawisz.")
Console.ReadKey(true) |> ignore
Linijki kodu w programie...
Console.WriteLine("Pi = {0:##.#####}.", Math.PI);
...oznaczają, że do wyświetlenia na ekranie liczby ? przeznaczono 7 pól, w tym 5 na część ułamkową {0:##.#####}. Klasa Math jest standardową klasą języka F#, umożliwiającą wykonanie różnych matematycznych obliczeń.
Rezultat działania programu można zobaczyć na rysunku 1.2.
Program wyświetla stałą pi z zadaną dokładnością.
Pi = 3,14159.
Rysunek 1.2. Efekt działania programu Zadanie 1.2
Do wyprowadzania wyników na ekran monitora można również skorzystać z funkcji printf i printfn. Stosując łańcuchy formatujące w postaci...
ciąg_formatujący wartości formatowane
...możemy otrzymać wyniki w wygodnej dla nas formie. Pokazuje to następujący fragment programu...
let pi = Math.PI
printfn "%f" pi
printfn "%1.1f" pi
printfn "%2.2f" pi
printfn "%2.8f" pi
...oraz rezultat jego działania:
3.141593
3.1
3.14
3.14159265
Więcej informacji na ten temat Czytelnik może znaleźć w publikacji [1] lub w Core.Printf Module (F#)11 na stronie internetowej https://msdn.microsoft.com/en-us/.
Zadanie 1.3
Napisz program, który wyświetla na ekranie komputera pierwiastek kwadratowy z wartości predefiniowanej ? = 3,14... z dokładnością do dwóch miejsc po przecinku.
Przykładowe rozwiązanie - listing 1.3
// Zadanie 1.3
open System
Console.WriteLine("Program wyświetla pierwiastek kwadratowy z liczby pi")
Console.WriteLine("z dokładnością do dwóch miejsc po przecinku.")
Console.WriteLine()
Console.WriteLine("Sqrt(pi) = {0:##.##}.", Math.Sqrt(Math.PI))
// Zatrzymanie konsoli
Console.WriteLine("Naciśnij dowolny klawisz.")
Console.ReadKey(true) |> ignore
Metoda Sqrt() pozwala na obliczenie pierwiastka kwadratowego. Jest ona metodą standardowej klasy Math.
Rezultat działania programu można zobaczyć na rysunku 1.3.
Program wyświetla pierwiastek kwadratowy z liczby pi
z dokładnością do dwóch miejsc po przecinku.
Sqrt(pi) = 1,77.
Rysunek 1.3. Efekt działania programu Zadanie 1.3
Oto przykłady zadań zawierające podstawowe działania arytmetyczne.
Zadanie 1.4
Napisz program, który oblicza wynik dzielenia całkowitego bez reszty dla dwóch liczb całkowitych: a = 37 i b = 11.
Wskazówka: w języku F# w przypadku zastosowania operatora dzielenia / dla liczb całkowitych reszta wyniku jest pomijana. Tak samo jest w językach imperatywnych: C/C++ i Java.
Przykładowe rozwiązanie - listing 1.4
// Zadanie 1.4
open System
let a = 37
let b = 11
Console.WriteLine("Program wyświetla wynik dzielenia całkowitego")
Console.WriteLine("bez reszty dwóch liczb całkowitych a i b.")
Console.WriteLine("Dla liczb: a = {0} i b = {1}", a, b)
Console.WriteLine("{0}/{1} = {2}.", a, b, a/b)
// Zatrzymanie konsoli
Console.WriteLine("Naciśnij dowolny klawisz.")
Console.ReadKey(true) |> ignore
Rezultat działania programu można zobaczyć na rysunku 1.4.
Program wyświetla wynik dzielenia całkowitego
bez reszty dwóch liczb całkowitych a i b.
Dla liczb: a = 37 i b = 11
37/11 = 3.
Rysunek 1.4. Efekt działania programu Zadanie 1.4
Zadanie 1.5
Napisz program, który oblicza resztę z dzielenia całkowitego dwóch liczb całkowitych: a = 37 i b = 11.
Wskazówka: należy zastosować operator reszty z całkowitego dzielenia całkowitego modulo, który oznaczamy w języku F# jako %. Podobnie jak w językach imperatywnych, takich jak C/C++ i Java, operator ten umożliwia uzyskanie tylko reszty z dzielenia, a wartość całkowita jest odrzucana.
Przykładowe rozwiązanie - listing 1.5
// Zadanie 1.5
open System
let a = 37
let b = 11
Console.WriteLine("Program oblicza resztę z dzielenia całkowitego")
Console.WriteLine("dwóch liczb całkowitych a i b.");
Console.WriteLine()
Console.WriteLine("Dla liczb: a = {0} i b = {1}", a, b)
Console.WriteLine("{0}%{1} = {2}.", a, b, a%b)
// Zatrzymanie konsoli
Console.WriteLine("Naciśnij dowolny klawisz.")
Console.ReadKey(true) |> ignore
Rezultat działania programu można zobaczyć na rysunku 1.5.
Program oblicza resztę z dzielenia całkowitego
dwóch liczb całkowitych a i b.
Dla liczb: a = 37 i b = 11
37%11 = 4.
Rysunek 1.5. Efekt działania programu Zadanie 1.5
Na koniec tego rozdziału została nam do omówienia jedna kwestia. Zajmiemy się nią w podrozdziale 1.3.