Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

pl:dydaktyka:pp:haskell:lab-more-funcs [2018/05/23 12:38]
esimon [Zadania]
pl:dydaktyka:pp:haskell:lab-more-funcs [2019/06/27 15:50]
Linia 1: Linia 1:
-====== 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ęć: <code haskell>​sum2c :: Int -> Int -> Int 
-sum2c m n = m + n</​code>​ to co się tutaj tak naprawdę dzieje to: <code haskell>​sum2c :: Int -> (Int -> Int)</​code>​ 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: <code haskell>​dodaj1 :: Int -> Int 
-dodaj1 = sum2c 1</​code>​ 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? <code haskell>​Prelude>​ :t map 
-map :: (a -> b) -> [a] -> [b]</​code>​ dodajmy do tego brakujący nawias, aby nie było wątpliwości co tutaj się dzieje:​ <code haskell>​map :: (a -> b) -> ([a] -> [b])</​code>​ 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ć: <code haskell>​map dodaj1 [1..10]</​code>​ nie musisz nawet tworzyć osobnej funkcji ''​dodaj1''​! map może przyjąć to co jest zwrócone przez ''​%%sum2c 1%%'':​ <code haskell>​map (sum2c 1) [1..10]</​code>​ możesz nawet pójść o krok dalej i stworzyć funkcję, która dodaje 1 do wszystkich elementów listy! <code haskell>​dodaj1doListy :: [Int] -> [Int] 
-dodaj1doListy = map dodaj1</​code> ​ 
- 
-==== 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ą: <code haskell>​map (* 2) (map dodaj1 [1..10])</​code>​ 
-  * 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):<​code haskell>​map ((* 2) . dodaj1) [1..10]</​code>​ 
-  * Sprawdź typ operatora ''​.''​ za pomocą: <code haskell>:​t (.)</​code>​ 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:<code haskell>​filter (<5) [1..10]</​code>​ 
-    * **''​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: <code haskell>​foldl (+) 0 [1..10]</​code>​ 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 ''​%%->​%%''​ <code haskell>​-- nazwana funkcja: 
-f x = x + 1 
--- dokładnie ta sama funkcjonalność zapisana jako funkcja anonimowa: 
-f = \x -> x+1</​code>​ 
-    * 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ą: <code haskell>​sum2lambda = \x y -> x + y</​code>​ 
- 
-  * 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 ''​%%$%%''​ : <code haskell>​sum (filter (> 10) (map (*2) [2..10])) ​ -- f (g (z x))</​code>​ 
-    * z wykorzystaniem ''​%%$%%''​ : <code haskell>​sum $ filter (> 10) $ map (*2) [2..10] ​ -- f $ g $ z x</​code>​ 
-  * 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 <code haskell>​[f x | x <- lista, p x]</​code>​ 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ę! <code haskell>​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</​code>​ 
-  - 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:** <code haskell>​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</​code>​ 
-    * **Przykład użycia:** <code haskell>>​ dodaj3 = generatorOperator (+) 3 
-> dodaj3 2 
-5 
-> podziel100 = generatorOperator (/) 100 
-> podziel100 8 
-12.5</​code>​ 
-  - Wykorzystując ''​fold''​ zdefiniuj funkcję odwracającą String. 
-    * **Nagłówek funkcji:** <code haskell>​reverse'​ :: String -> String</​code>​ 
-    * **Przykład użycia:** <code haskell>>​ reverse'​ "​Kocham Haskella"​ 
-"​alleksaH mahcoK"​ 
-> reverse'​ "​kobyla ma maly bok" 
-"kob ylam am alybok"</​code>​ 
-  - 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:** <code haskell>​policzISumuj :: (Int -> Int) -> Int -> Int -> Int</​code>​ 
-    * **Przykład użycia:** <code haskell>>​policzISumuj (^2) 1 10 
-385 
-> policzISumuj (\x -> 42) 123 127 
-210</​code>​ 
-  - Wykorzystując funkcję ''​filter''​ oraz lambdy, stwórz funkcję wybierającą z zadanej listy liczby pierwsze. 
-    * **Nagłówek funkcji:** <code haskell>​pierwsze :: [Int] -> [Int]</​code>​ 
-    * **Przykład użycia:** <code haskell>>​ pierwsze [100..110] 
-[101,​103,​107,​109] 
-> take 15 $ pierwsze [1..] 
-[2,​3,​5,​7,​11,​13,​17,​19,​23,​29,​31,​37,​41,​43]</​code>​ 
-  - 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:** <code haskell>​conajmniejn'​ :: [Int] -> Int -> [Int]</​code>​ 
-    * **Przykład użycia:** <code haskell>>​ conajmniejn'​ [4,​5,​2,​5,​4,​3,​1,​3,​4] 2 
-[5,3,4] 
-> conajmniejn'​ [4,​5,​2,​5,​4,​3,​1,​3,​4] 4 
-[]</​code>​ 
pl/dydaktyka/pp/haskell/lab-more-funcs.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0