Programowanie Funkcyjne C#

Co to jest?Część NR.1Programowanie funkcyjne istnieje od bardzo długiego czasu. Język programowania LISP w 1958 roku postawił punkt startowy w programowaniu funkcjonalnym.  Z drugiej strony LISP już wtedy bazował na koncepcjach, które istniały. Pierwsze kroki w kierunku programowania funkcjonalnego  można datować na lata 1930-1940. Wtedy Alonzo Vhruch opracował lambda Calcus.

http://en.wikipedia.org/wiki/Lambda_calculus*

Co to jest programowanie Funkcyjne

Programowanie funkcje jest kolejnym paradygmatem programowania, który występuje w wielu językach programowania. Jak sama nazwa wskazuje koncentruje się na funkcjach.

Dla niektórych programistów programowanie funkcyjne wydaje się być naturalne. Funkcyjne programowanie według nich bardziej koncentruje się na problemie, który powinien być rozwiązany niż na poszczególnych krokach rozwiązania problemu.

#2 DelegateCzęść NR.2

C# jest językiem obiektowym. C# nie pozwala na użycie “funkcji” poza klasami. Jest to wielka różnica w porównaniu z C++ - pierwszym językiem programowania zorientowanym obiektowo. C++ pozwala na pojawianie się funkcji poza klasami ze względu na wsteczną kompatybilność z językiem C.

Można się kłócić, że skoro C# ma klasy statyczne z statycznymi metodami to programowanie imperatywne istnieje, ale jest ono ukryte za obiektową terminologią.

Funkcje w C# mogą istnieć tylko wewnątrz klas i co ważniejsze nie nazywamy je funkcjami, tylko metodami. Metody w C# zawsze muszą coś zwracać. Na szczęście ta zasada jest omijana poprzez typ void. Dzięki temu w metodzie nie musimy używać słowa kluczowego return.

#3 LambdaCzęść NR.3

Nie wszystkie funkcje są tak ważne, że muszą mieć nazwę. C# wspiera anonimowe funkcje od wersji 2.0. A od wersji 3.0 anonimowe funkcje pojawiły się w lepszej wersji. W wersji wyrażeń lambda.

Te funkcje nie żyją na poziomie klas i dlatego nie mają nazw. Referencje do takich funkcji zazwyczaj przetrzymuje się w postaci zmiennych typu danej delegaty.

Istnieją pewne ograniczenia dotyczące tego, gdzie anonimowe funkcje mogą się znajdować i jakie mają być.

Przykładowo nie mogą one być generyczne.

ClousersCzęść NR.4

Aby język programowania mógł działać z Funkcjami wyższego rzędu musi rozwiązać problem z zasięgiem zmiennych. O co chodzi?

Funkcja wyższego rzędu jest to funkcja, która przyjmuje jako parametry kolejne funkcje. Co się jednak dzieje z parametrami zaszytymi w wewnętrznych parametrach funkcji?

Kiedy funkcje są przekazywane jako parametry, zmienne, wartości zwracane - wtedy kompilator używa domknięcia, aby rozszerzyć zasięg zmiennych tak, by były one dostępne wtedy, gdy są potrzebne.

W tym wpisie spojrzymy na problem z zasięgiem zmiennych i na domknięcia, ale najpierw…zobaczymy jak w C# dynamicznie tworzy się funkcję.

#6 Kod to...Część NR.5

Kiedyś szczytem wszystkich wynalazków stworzonych przez programistyczne języki funkcjonalne była możliwość wykonania funkcji, gdy ona jest w formie napisu string.

Ten napis można było zmieniać i później go wywołać jak zwyczajny kod.

Inaczej mówiąc “eval” .

Wiele języków programowania wspiera operację “eval” czyli możliwość wywołania funkcji w danym języku, gdy dynamicznie tworzymy wyrażenie tej własnej funkcji.

Najprostszy eval jak powiedzieliśmy wcześniej wymaga do operacji zwykłego napisu string. Języki, które to wspierają pozwalają na wywołanie takiego kodu. Oto przykładowy kod JavaScript.

CreateCzęść NR.6 Pomówmy o wyrażeniach drzewiastych raz jeszcze. Wyrażenia drzewiaste są naprawdę potężnym narzędziem, ponieważ traktują kod jak dane. W poprzednim wpisie przyjrzeliśmy się jak wyrażenia drzewiaste są zbudowane i jak je wykorzystać.

W tym wpisie skoncentrujemy się na tworzeniu wyrażeń, jak i ich zmianie.

Tematyka ta jest dosyć obszerna dlatego postanowiłem przygotować tylko parę ciekawych przykładów, które wykazują pewną użyteczność tworzenia dynamicznych wyrażeń.

CurryingCzęść NR.7 Gdzie leży serce programowania funkcjonalnego. Oczywiście w jego funkcjach, które są składową większego algorytmu. Haskell Curry był matematykiem i to od niego wywodzi się termin Currying, jak i cały język programowania Haskell.

Currying sprawia, że jesteśmy w stanie zobaczyć wszystkie funkcje jako funkcje jednoparametrowe bez względu na to, ile parametrów tak naprawdę potrzebujemy do wyliczeń i działania.

Jak to jest możliwe? Przecież gdzieś te parametry muszą być? Jest to jednak prostsze niż się wydaje.

Otwiera nas to na dzielenie aplikacji  na mniejsze elementy. Jest to jedna z głównych esencji każdego języka funkcjonalnego.  Jak to wygląda w C#, który do końca nie jest językiem funkcjonalnym.

Currying howCzęść NR.8 W poprzednim wpisie widzieliśmy przykłady jak Currying działa. Przykłady te były opisane na zmiennych zadeklarowanych, albo jako anonimowe metody, albo jako wyrażenia lambda. Zmieniliśmy te funkcje do postaci łańcucha wywołań funkcji, które przyjmują zawsze tylko jeden parametr.

Jak to jednak by wyglądało w C# , w kontekście klas. Jak mieć metodę Currying w stylu języków funkcjonalnych.

Jakie ja napotkałem problemy pisząc ten prosty przykład edukacyjny.

RekurecjaCzęść NR.9 W funkcjonalnych językach programowania rekurencja jest narzędziem z dużą tradycją. Wiele oryginalnych języków funkcjonalnych nie miało konstrukcji pętli. W takich wypadkach była używana rekurencja.

Obecnie wiele języków funkcjonalnych ma definicję pętli w swojej składni.

Nie zmienia to jednak faktu, że rekurencja wciąż jest używana przez wielu programistów.

OrderCzęść NR.11 Czy wiesz, że mam na tym blog kurs Funkcyjnego programowania w C#. Dawno go nie odświeżałem, a wraz z pojawieniem się C# 9.0 myślę, że warto robi wpisy, które potrafią przełożyć filozofię funkcyjnego programowania na C#.  A sam C# tak jak przewidziałem na początku tego kursu będzie się zbliżał do trendów funkcyjnego programowania i brał pomysły z F#.

Dzisiaj po wielkim powrocie omówimy Funkcje wyższego rzędu (Higher-order function).

Jak wpiszesz "funkcje wyższego rzędu" do Google to zgłoszą Ci języki jak : Python, JavaScript. A jak to ma wyglądać w C#.

Funkcje wyższego rzędu są filarem funkcyjnego programowania.  Są to funkcje, które biorą albo inne funkcje jako parametry, albo zwracają nowe funkcję, które mogą być tworzone dynamicznie.

MemoryCzęść NR.12 Komputery są dobre w zapamiętywaniu rzeczy. Gdy patrzysz na jakąś aplikację biznesową to zapewne nie jesteś zafascynowany tym co ona może wyliczyć, ale co ona może zapamiętać. To główny cel naszych aplikacji wyświetlić wcześniej zapamiętane dane. 

Gdzie te dane są? W plikach, w bazach danych? 

Witaj w kolejnym wpisie z kursu programowania funkcyjnego w C#?

To niesamowite jak długo prowadzę ten cykl. 

W programowaniu funkcjonalnym koncentruję się na wykonywaniu samych funkcji niż na przetrzymywania stanów pośrednich. Funkcje w programowaniu funkcyjnym mają być czystymi funkcjami.

Funkcyjne programowanie polega na uniknięciu ukrytych wejść i wyjść jak:

  • Używanie Wyjątków, aby kontrolować przepływ programu
  • Funkcja nie modyfikuje stanu
  • Funkcja nie trzyma referencji do obiektu, który miałby być zmieniony
  • Funkcja nie zapisuje czegokolwiek w polach statycznych

Z drugiej strony pamiętanie stanów jest częścią aplikacji. Zawsze gdzieś będzie warstwa dostępu do danych. 

Mamy jednak kolejne ciekawe założenie programowania funkcyjnego. Zakłada ona, że raz wyliczona nie powinna zostać wyliczana ponownie. Możemy też założyć, że raz pobrana rzecz z bazy danych nie powinna być także odczytana ponownie do jakiegoś limitu czasu.

Czyli mówimy o Cache w naszej aplikacji o możliwości pamiętania tego, co już zrobiliśmy wcześniej aby nie robić czegoś ponownie z takim samym rezultatem.

Mamy dwa style zapamiętywania rzeczy i omówimy je w tym wpisie.

RememberCzęść NR.13 W poprzednim wpisie omówiliśmy techniki z programowania funkcyjnego jak Precomputation i Memoization. Pytanie, jak te techniki mają się do kodu w C#, który jest asynchroniczny i opiera się na obiektach takich jak Task, które symbolizują w C# przyszłość.

Jak się domyślasz Memoizacja może być bardzo utrudniona. W poprzednim wpisie nawet zaznaczyłem, że same słowniki Dictionary nie są wielowątkowo bezpieczne.

Trzeba by było korzystać z innych słowników, które są bardziej bezpieczne jak ConcurrentDictionary

A co z Precomputation? Nie wiem czy wiesz, ale sam obiekt klasy Task wspiera tą filozofię aby nie wyliczać ponownie czegoś co już zostało zrobione.

Wszystkie Kategorie