====== 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 []