====== Lab: Więcej funkcji ======
Tematyka:
* funkcje wyższego rzędu
* składanie funkcji
* funkcje anonimowe (lambdy)
* funkcje: map, fold, filter
===== Wprowadzenie =====
==== Funkcje wyższego rzędu ====
* Większość języków programowania pozwala na tworzenie funkcji, które mają dowolną liczbę argumentów, ale to jest niepotrzebne. W Haskellu wszystkie funkcje przyjmują dokładnie **jeden** argument, nie tracąc przy tym na ekspresywności języka. Nazywa się to rozwijaniem funkcji (ang. //currying//, na cześć [[wp>Haskell_Curry|Haskella Curry'ego]]; zgadnij skąd wzięła się nazwa języka "Haskell" :-D)
* Aby to działało czynimy użytek z **funkcji wyższego rzędu**, czyli funkcji, które zwracają funkcje. Spotkaliśmy się z nimi już wcześniej, wróćmy do funkcji sumującej z poprzednich zajęć: sum2c :: Int -> Int -> Int
sum2c m n = m + n
to co się tutaj tak naprawdę dzieje to: sum2c :: Int -> (Int -> Int)
czyli funkcja ''%%sum2c%%'' przyjmuje argument typu ''%%Int%%'' i zwraca funkcję, której typem jest ''%%Int -> Int%%''
* Co nam to daje?
* Skoro po przyjęciu pierwszego argumentu zwracana jest funkcja i do niej dopiero jest aplikowany kolejny argument, poprawnym wyrażeniem będzie: dodaj1 :: Int -> Int
dodaj1 = sum2c 1
dodaj1 jest funkcją zwróconą przez ''%%sum2c 1%%'', czyli funkcją dodającą 1 do swojego argumentu\\ //Takie częściowe określenie argumentów funkcji nazywa się [[wp>Partial_application]]//
* Idźmy dalej: pamiętasz funkcję ''%%map%%'', którą implementowaliśmy na pierwszych zajęciach? Jaki jest jej typ? ghci> :t map
map :: (a -> b) -> [a] -> [b]
dodajmy do tego brakujący nawias, aby nie było wątpliwości co tutaj się dzieje: map :: (a -> b) -> ([a] -> [b])
czyli: map przyjmuje jako swój argument funkcję o typie ''%%a -> b%%'' i zwraca funkcję o typie ''%%[a] -> [b]%%'', czyli jak najbardziej możemy wywołać: map dodaj1 [1..10]
nie musisz nawet tworzyć osobnej funkcji ''dodaj1''! map może przyjąć to co jest zwrócone przez ''%%sum2c 1%%'': map (sum2c 1) [1..10]
możesz nawet pójść o krok dalej i stworzyć funkcję, która dodaje 1 do wszystkich elementów listy! dodaj1doListy :: [Int] -> [Int]
dodaj1doListy = map dodaj1
==== Składanie funkcji ====
* Co w sytuacji kiedy chcemy zrobić więcej niż jedną operację na każdym z elementów listy? Nie tylko dodać 1, ale później również pomnożyć przez 2?
* Możemy oczywiście wykonać najpierw jedną operację na wszystkich elementach listy, a następnie drugą: map (* 2) (map dodaj1 [1..10])
* Możemy też wykorzystać **składanie funkcji** wykonywane za pomocą **operatora ''.'' (kropka)**. Poniższy kod robi dokładnie to samo (ale w jednej iteracji po liście):map ((* 2) . dodaj1) [1..10]
* Sprawdź typ operatora ''.'' za pomocą: :t (.)
czy rozumiesz ten typ?
==== Jeszcze trochę o funkcjach: listy, lambdy, $ ====
* W Haskellu mamy trzy podstawowe funkcje robiące coś na listach:
* **''map''** -- wywołuje funkcję podaną jako argument na każdym z elementów; tę funkcję już znasz
* **''filter''** -- sprawdza czy każdy z elementów spełnia zadany warunek (czy funkcja podana jako argument zwróciła True), np. możemy wybrać z listy wszystkie elementy mniejsze od 5:filter (<5) [1..10]
* **''fold''** -- przechodzi po wszystkich elementach, wykonując zadaną operację, a wynik zapisując do akumulatora; tak naprawdę jest to cała rodzina funkcji różniących się sposobem przechodzenia po liście -- dwie podstawowe to foldl i foldr, przechodzące po liście odpowiednio od lewej i od prawej strony, np. możemy zsumować wszystkie elementy listy: foldl (+) 0 [1..10]
gdzie (+) jest funkcją, a 0 jest akumulatorem, do którego będą dodawane kolejne elementy listy -> ładne wyjaśnienie graficzne działania fold można znaleźć [[http://www.cse.unsw.edu.au/~en1000/haskell/hof.html|na stronie]]
* Przypisywanie nazwy do każdej funkcji mija się z celem - niektóre funkcje tworzymy ad hoc do jednorazowego wykorzystania, np. do funkcji map/fold/filter i przypisywanie ich do nazwy to marnowanie zasobów (i zwiększanie długości kodu). W takiej sytuacji możemy definiować **funkcje anonimowe (lambdy)** -- znasz je już pewnie z innych języków programowania.
* W Haskellu lambdy rozpoczynamy od ''\'' oraz wykorzystujemy operator ''%%->%%'' -- nazwana funkcja:
f x = x + 1
-- dokładnie ta sama funkcjonalność zapisana jako funkcja anonimowa:
f = \x -> x+1
* Możemy też zdefiniować funkcję anonimową, która przyjmuje więcej niż jeden argument (tzn. funkcję, która zwraca funkcję, itd). Wracając do przykładu z funkcją sumującą: sum2lambda = \x y -> x + y
* Jeżeli jeszcze tego nie pamiętasz (a już po pierwszych zajęciach powinno to się wryć w pamięć :!:) -- największy priorytet w Haskellu ma spacja, czyli aplikacja argumentu do funkcji. Efektem jest to, że czasami potrzeba dużo nawiasów, aby zrealizować złożone wywołanie, aby działało zgodnie z naszymi założeniami. Z pomocą przychodzi tutaj operator ''%%$%%'', który ma bardzo niski priorytet + co najważniejsze: jest łączony z prawej strony, a nie z lewej jak spacja. Co nam to daje? Porównaj dwa wywołania:
* bez ''%%$%%'' : sum (filter (> 10) (map (*2) [2..10])) -- f (g (z x))
* z wykorzystaniem ''%%$%%'' : sum $ filter (> 10) $ map (*2) [2..10] -- f $ g $ z x
* Jest to tylko //syntactic sugar// - kod jest czytelniejszy i ładniejszy (i bardziej Haskell-way), ale nie wnosi to nowej funkcjonalności
* Przydatne mogą być słowa kluczowe ''where'' i ''let'' -> możesz o nich poczytać [[http://learnyouahaskell.com/syntax-in-functions#where|tutaj]]
===== Zadania =====
- Dane jest wyrażenie [f x | x <- lista, p x]
Zdefiniuj własną funkcję wykorzystującą wybrane z funkcji: map, fold, filter, aby otrzymać dokładnie taki sam efekt. Aby to przetestować możesz skorzystać z poniższego kodu -- funkcje ''%%mojeLiczby%%'' i ''%%mojeLiczby'%%'' powinny zwracać w efekcie dokładnie tę samą listę! mojeLiczby = [f x | x <- lista, p x]
where f = \a -> 2 * a -- f mnoży liczbę razy 2
lista = [1..10] -- lista początkowa
p = \b -> b `mod` 2 == 0 -- p wybiera liczby parzyste
mojeLiczby' = -- TUTAJ WPISZ SWOJĄ FUNKCJĘ!
where f = \a -> 2 * a
lista = [1..10]
p = \b -> b `mod` 2 == 0
- Zdefiniuj funkcję anonimową, która przyjmuje jako argument operator oraz liczbę i zwraca funkcję wykonującą daną operację matematyczną. Przypisz tę funkcję do nazwy ''generatorOperator''.
* **Nagłówek funkcji:** generatorOperator :: (lewa -> prawa -> wynik) -> lewa -> (prawa -> wynik)
-- funkcja przyjmuje operator, który jest typu (lewa -> prawa -> wynik)
-- oraz lewą część operatora i zwraca funkcję, która przyjmuje prawą część operatora i zwraca wynik
* **Przykład użycia:** ghci> dodaj3 = generatorOperator (+) 3
ghci> dodaj3 2
5
ghci> podziel100 = generatorOperator (/) 100
ghci> podziel100 8
12.5
- Wykorzystując ''fold'' zdefiniuj funkcję odwracającą String.
* **Nagłówek funkcji:** myReverse :: String -> String
* **Przykład użycia:** ghci> myReverse "Kocham Haskella"
"alleksaH mahcoK"
ghci> myReverse "kobyla ma maly bok"
"kob ylam am alybok"
- Napisz funkcję ''policzISumuj'', która przyjmuje trzy argumenty: funkcję, którą ma zaaplikować do każdego z elementów listy oraz pierwszy i ostatni element zakresu dla którego ma zostać zastosowana. W wyniku funkcja zwraca sumę wyników zaaplikowania funkcji do każdego z elementów.
* **Nagłówek funkcji:** policzISumuj :: (Int -> Int) -> Int -> Int -> Int
* **Przykład użycia:** ghci>policzISumuj (^2) 1 10
385
ghci> policzISumuj (\x -> 42) 123 127
210
- Wykorzystując funkcję ''filter'' oraz lambdy, stwórz funkcję wybierającą z zadanej listy liczby pierwsze.
* **Nagłówek funkcji:** pierwsze :: [Int] -> [Int]
* **Przykład użycia:** ghci> pierwsze [100..110]
[101,103,107,109]
ghci> take 15 $ pierwsze [1..]
[2,3,5,7,11,13,17,19,23,29,31,37,41,43]
- Korzystając ze swojej aktualnej wiedzy napisz jeszcze raz funkcję ''conajmniejn'' z poprzedniego laboratorium, ale tym razem jako **jedną funkcję bez wykorzystania funkcji pomocniczych** (__dodatkowe utrudnienie dla zainteresowanych:__ zrób to bez korzystania z funkcji ''[[http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-List.html#v:nub|nub]]''). Dla przypomnienia:
* **Nagłówek funkcji:** conajmniejn2 :: [Int] -> Int -> [Int]
* **Przykład użycia:** ghci> conajmniejn2 [4,5,2,5,4,3,1,3,4] 2
[5,3,4]
ghci> conajmniejn2 [4,5,2,5,4,3,1,3,4] 4
[]