learnxinyminutes-docs/pl-pl/haskell-pl.html.markdown

445 lines
13 KiB
Haskell
Raw Normal View History

2017-01-10 08:45:11 +00:00
---
language: Haskell
lang: pl-pl
contributors:
- ["Remigiusz Suwalski", "https://github.com/remigiusz-suwalski"]
---
2017-01-13 17:05:03 +00:00
Haskell został zaprojektowany jako praktyczny, czysto funkcyjny język
2017-01-10 08:45:11 +00:00
programowania. Jest znany przede wszystkim ze względu na jego monady oraz system
typów, ale ja lubię do niego wracać przez jego elegancję. Sprawił on, że
programowanie jest prawdziwą przyjemnością.
```haskell
-- Komentarze jednolinijkowe zaczynają się od dwóch myślników
{- Komentarze wielolinijkowe należy
zamykać w bloki klamrami.
-}
----------------------------------------------------
2017-01-10 09:04:14 +00:00
-- 1. Podstawowe typy danych oraz operatory
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-10 09:04:14 +00:00
-- Mamy liczby
2017-01-10 08:45:11 +00:00
3 -- 3
2017-01-10 09:04:14 +00:00
-- Podstawowe działania działają tak, jak powinny
2017-01-10 08:45:11 +00:00
1 + 1 -- 2
8 - 1 -- 7
10 * 2 -- 20
35 / 5 -- 7.0
2017-01-10 09:04:14 +00:00
-- dzielenie domyślnie zwraca ,,dokładny'' wynik
2017-01-10 08:45:11 +00:00
35 / 4 -- 8.75
2017-01-10 09:04:14 +00:00
-- dzielenie całkowitoliczbowe
2017-01-10 08:45:11 +00:00
35 `div` 4 -- 8
2017-01-10 09:04:14 +00:00
-- wartości logiczne także są podstawowym typem danych:
2017-01-10 08:45:11 +00:00
True
False
2017-01-10 09:04:14 +00:00
-- operacje logiczne: negacja oraz porównania
2017-01-10 08:45:11 +00:00
not True -- False
not False -- True
1 == 1 -- True
1 /= 1 -- False
1 < 10 -- True
2017-01-10 09:04:14 +00:00
-- W powyższych przykładach, `not` jest funkcją przyjmującą jeden argument.
-- Haskell nie potrzebuje nawiasów, by wywołać funkcję: argumenty są po prostu
-- wypisywane jeden za drugim. Ogólnie wygląda to tak:
-- funkcja arg1 arg2 arg3...
-- Sekcja poświęcona funkcjom zawiera informacje, jak stworzyć własne.
2017-01-10 08:45:11 +00:00
2017-01-10 09:04:14 +00:00
-- Łańcuchy znaków (stringi) i pojedyncze znaki:
"To jest lancuch."
'a' -- znak
'Nie mozna laczyc apostrofow z lancuchami.' -- błąd!
2017-01-10 08:45:11 +00:00
2017-01-10 09:04:14 +00:00
-- Łańcuchy można sklejać
2017-01-10 08:45:11 +00:00
"Hello " ++ "world!" -- "Hello world!"
2017-01-10 09:04:14 +00:00
-- Łańcuch jest listą własnych znaków
2017-01-10 08:45:11 +00:00
['H', 'e', 'l', 'l', 'o'] -- "Hello"
2017-01-10 09:04:14 +00:00
"To jest lancuch" !! 0 -- 'T'
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-10 08:58:12 +00:00
-- Listy oraz krotki
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-10 08:58:12 +00:00
-- Wszystkie elementy listy muszą być tego samego typu.
-- Poniższe dwie listy są identyczne:
2017-01-10 08:45:11 +00:00
[1, 2, 3, 4, 5]
[1..5]
2017-01-10 08:58:12 +00:00
-- Zakresy są uniwersalne.
2017-01-10 08:45:11 +00:00
['A'..'F'] -- "ABCDEF"
2017-01-10 08:58:12 +00:00
-- Przy tworzeniu zakresów można określić krok.
2017-01-10 08:45:11 +00:00
[0,2..10] -- [0, 2, 4, 6, 8, 10]
2017-01-10 08:58:12 +00:00
[5..1] -- To nie zadziała, gdyż w Haskellu zakresy tworzone są domyślnie rosnąco
2017-01-10 08:45:11 +00:00
[5,4..1] -- [5, 4, 3, 2, 1]
2017-01-10 08:58:12 +00:00
-- indeksowanie listy od zera
2017-01-10 08:45:11 +00:00
[1..10] !! 3 -- 4
2017-01-10 08:58:12 +00:00
-- Można nawet tworzyć listy nieskończone!
[1..] -- lista wszystkich liczb naturalnych
2017-01-10 08:45:11 +00:00
2017-01-10 08:58:12 +00:00
-- Nieskończone listy mają prawo działać, ponieważ Haskell cechuje się leniwym
-- wartościowaniem. To oznacza, że obliczane są jedynie te elementy listy,
-- których istotnie potrzebujemy. Możemy poprosić o tysiączny element i
-- dostaniemy go:
2017-01-10 08:45:11 +00:00
[1..] !! 999 -- 1000
2017-01-10 08:58:12 +00:00
-- Haskell wyznaczył pierwsze tysiąc elementów listy, ale cała jej reszta
-- jeszcze nie istnieje! Nie zostanie obliczona ich wartość, póki nie zajdzie
-- taka potrzeba.
2017-01-10 08:45:11 +00:00
2017-01-10 08:58:12 +00:00
-- łączenie dwóch list
2017-01-10 08:45:11 +00:00
[1..5] ++ [6..10]
2017-01-10 08:58:12 +00:00
-- dodawanie pojedynczego elementu na początek listy
2017-01-10 08:45:11 +00:00
0:[1..5] -- [0, 1, 2, 3, 4, 5]
2017-01-10 08:58:12 +00:00
-- więcej operacji na listach
2017-01-10 08:45:11 +00:00
head [1..5] -- 1
tail [1..5] -- [2, 3, 4, 5]
init [1..5] -- [1, 2, 3, 4]
last [1..5] -- 5
-- list comprehensions
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]
2017-01-10 08:58:12 +00:00
-- z dodatkowym warunkiem
2017-01-10 08:45:11 +00:00
[x*2 | x <- [1..5], x*2 > 4] -- [6, 8, 10]
2017-01-10 08:58:12 +00:00
-- każdy element krotki może być innego typu, jednak sama krotka musi być stałej
-- długości. Przykładowo:
2017-01-10 08:45:11 +00:00
("haskell", 1)
2017-01-10 08:58:12 +00:00
-- dostęp do elementów pary (krotki długości 2):
2017-01-10 08:45:11 +00:00
fst ("haskell", 1) -- "haskell"
snd ("haskell", 1) -- 1
----------------------------------------------------
2017-01-13 09:47:08 +00:00
-- 3. Funkcje
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-13 09:47:08 +00:00
-- Prosta funkcja przyjmująca dwa argumenty
2017-01-10 08:45:11 +00:00
add a b = a + b
2017-01-13 09:47:08 +00:00
-- Pamiętaj, że podczas stosowania ghci, interpretera Haskella, wszelkie
-- definicje muszą zostać poprzedzone słowem `let`, na przykład:
2017-01-10 08:45:11 +00:00
-- let add a b = a + b
2017-01-13 09:47:08 +00:00
-- Używanie funkcji:
2017-01-10 08:45:11 +00:00
add 1 2 -- 3
2017-01-13 09:47:08 +00:00
-- Nazwę funkcji można podać między dwoma argumentami, ale wtedy musi zostać
-- otoczona grawisami:
2017-01-10 08:45:11 +00:00
1 `add` 2 -- 3
2017-01-13 09:47:08 +00:00
-- Nazwa funkcji nie musi zawierać żadnych liter, przykładem czego jest
-- operator dzielenia:
2017-01-10 08:45:11 +00:00
(//) a b = a `div` b
35 // 4 -- 8
2017-01-13 09:47:08 +00:00
-- Strażnicy: prosty sposób na rozbijanie funkcji na przypadki
2017-01-10 08:45:11 +00:00
fib x
| x < 2 = 1
| otherwise = fib (x - 1) + fib (x - 2)
2017-01-13 09:47:08 +00:00
-- Dopasowanie wzorca jest podobne. Haskell sam automatycznie wybierze, która
-- z poniższych definicji fib powinna zostać użyta:
2017-01-10 08:45:11 +00:00
fib 1 = 1
fib 2 = 2
fib x = fib (x - 1) + fib (x - 2)
2017-01-13 09:47:08 +00:00
-- Dopasowanie z krotkami:
2017-01-10 08:45:11 +00:00
foo (x, y) = (x + 1, y + 2)
2017-01-13 09:47:08 +00:00
-- Dopasowanie z listami. Tutaj `x` jest pierwszym elementem listy,
-- natomiast `xs` to jej reszta (ogon). Poniższa funkcja nakłada funkcję
-- na każdy z elementów listy:
2017-01-10 08:45:11 +00:00
myMap func [] = []
myMap func (x:xs) = func x:(myMap func xs)
2017-01-13 09:47:08 +00:00
-- Funkcje anonimowe tworzone są przy użyciu w-tył-ciachu, po którym następują
-- wszystkie argumenty:
2017-01-10 08:45:11 +00:00
myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]
2017-01-13 09:47:08 +00:00
-- używanie zwijania z anonimowymi funkcjami: foldl1 zwija z lewej strony,
-- przyjmując jako wartość początkową zbieracza pierwszy element listy.
2017-01-10 08:45:11 +00:00
foldl1 (\acc x -> acc + x) [1..5] -- 15
----------------------------------------------------
2017-01-13 09:31:24 +00:00
-- 4. Więcej funkcji
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-13 09:31:24 +00:00
-- częściowe nakładanie: jeśli funkcja nie otrzyma wszystkich swoich argumentów,
-- zostaje cześciowo nałożona - zwraca funkcję, która przyjmuje pozostałe,
-- brakujące argumenty.
2017-01-10 08:45:11 +00:00
add a b = a + b
2017-01-13 09:31:24 +00:00
foo = add 10 -- foo jest teraz funkcją, która przyjmuje liczbę, zwiększa ją o 10
2017-01-10 08:45:11 +00:00
foo 5 -- 15
2017-01-13 09:31:24 +00:00
-- Inny sposób na zapisanie tego samego:
2017-01-10 08:45:11 +00:00
foo = (10+)
foo 5 -- 15
2017-01-13 09:31:24 +00:00
-- składanie funkcji:
-- operator `.` składa wiele funkcji w jedną.
-- Dla przykładu, foo jest funkcją, która powiększa swój argument o 10, mnoży
-- tak uzyskaną liczbę przez 4 i zwraca wynik:
2017-01-10 08:45:11 +00:00
foo = (4*) . (10+)
-- 4*(10 + 5) = 60
foo 5 -- 60
2017-01-13 09:31:24 +00:00
-- ustalanie kolejności
-- Haskell posiada inny operator, `$`, który nakłada funkcję do podanego
-- parametru. W przeciwieństwie do zwykłego lewostronnie łącznego nakładania
-- funkcji, którego priorytet jest najwyższy (10), operator `$` posiada
-- priorytet 0 i jest prawostronnie łączny. Tak niski priorytet oznacza, że
-- wyrażenie po prawej traktowane jest jako parametr funkcji po lewej
2017-01-10 08:45:11 +00:00
2017-01-13 09:31:24 +00:00
-- wcześniej
even (fib 7) -- fałsz
2017-01-10 08:45:11 +00:00
2017-01-13 09:31:24 +00:00
-- równoważnie
even $ fib 7 -- fałsz
2017-01-10 08:45:11 +00:00
2017-01-13 09:31:24 +00:00
-- składanie funkcji
even . fib $ 7 -- fałsz
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-10 09:10:04 +00:00
-- 5. Sygnatury typów
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-10 09:10:04 +00:00
-- Haskell posiada wyjątkowo silny system typów, w którym każde poprawne
-- wyrażenie ma swój typ.
2017-01-10 08:45:11 +00:00
2017-01-10 09:10:04 +00:00
-- Kilka podstawowych typów:
2017-01-10 08:45:11 +00:00
5 :: Integer
"hello" :: String
True :: Bool
2017-01-10 09:10:04 +00:00
-- Funkcje też są określonego typu.
-- `not` przyjmuje wartość logiczną i taką też zwraca:
2017-01-10 08:45:11 +00:00
-- not :: Bool -> Bool
2017-01-10 09:10:04 +00:00
-- Przykład funkcji przyjmującej dwa argumenty
2017-01-10 08:45:11 +00:00
-- add :: Integer -> Integer -> Integer
2017-01-10 09:10:04 +00:00
-- Dobrą praktyką podczas definiowania wartości jest napisanie nad nią
-- także jej typu:
2017-01-10 08:45:11 +00:00
double :: Integer -> Integer
double x = x * 2
----------------------------------------------------
2017-01-10 17:29:33 +00:00
-- 6. Wyrażenia warunkowe
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-10 17:29:33 +00:00
-- wyrażenie warunkowe
haskell = if 1 == 1 then "wspaniale" else "paskudnie" -- haskell = "wspaniale"
2017-01-10 08:45:11 +00:00
2017-01-10 17:29:33 +00:00
-- wyrażenie warunkowe można rozbić na wiele linii,
-- ale trzeba uważać na wcięcia w kodzie
2017-01-10 08:45:11 +00:00
haskell = if 1 == 1
2017-01-10 17:29:33 +00:00
then "wspaniale"
else "paskudnie"
2017-01-10 08:45:11 +00:00
2017-01-10 17:29:33 +00:00
-- rozpatrywanie przypadków: oto jak można parsować argumenty z linii poleceń:
2017-01-10 08:45:11 +00:00
case args of
"help" -> printHelp
"start" -> startProgram
_ -> putStrLn "bad args"
2017-01-10 17:29:33 +00:00
-- Haskell zastępuje pętle (których nie ma) rekurencyjnymi wywołaniami funkcji.
-- map aplikuje funkcję do każdego elementu listy:
2017-01-10 08:45:11 +00:00
map (*2) [1..5] -- [2, 4, 6, 8, 10]
2017-01-10 17:29:33 +00:00
-- możesz zdefiniować funkcję for przy użyciu map:
2017-01-10 08:45:11 +00:00
for array func = map func array
2017-01-10 17:29:33 +00:00
-- a następnie użyć jej:
2017-01-10 08:45:11 +00:00
for [0..5] $ \i -> show i
2017-01-10 17:29:33 +00:00
-- mogliśmy użyć krótszego zapisu bez zmiany działania funkcji for:
2017-01-10 08:45:11 +00:00
for [0..5] show
2017-01-10 17:29:33 +00:00
-- Do redukcji listy służy polecenie foldl (foldr):
2017-01-10 08:45:11 +00:00
-- foldl <fn> <initial value> <list>
foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43
2017-01-10 17:29:33 +00:00
-- Jest to równoważne z:
2017-01-10 08:45:11 +00:00
(2 * (2 * (2 * 4 + 1) + 2) + 3)
2017-01-10 17:29:33 +00:00
-- foldl składa od od lewej strony, foldr od prawej
2017-01-10 08:45:11 +00:00
foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16
2017-01-10 17:29:33 +00:00
-- To zaś równoważne jest:
2017-01-10 08:45:11 +00:00
(2 * 1 + (2 * 2 + (2 * 3 + 4)))
----------------------------------------------------
2017-01-10 09:06:27 +00:00
-- 7. Typy danych
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-10 09:06:27 +00:00
-- Oto jak tworzy się nowe typy danych w Haskellu:
2017-01-10 08:45:11 +00:00
data Color = Red | Blue | Green
2017-01-10 09:06:27 +00:00
-- Teraz można używać ich we własnych funkcjach:
2017-01-10 08:45:11 +00:00
say :: Color -> String
say Red = "You are Red!"
say Blue = "You are Blue!"
say Green = "You are Green!"
2017-01-10 09:06:27 +00:00
-- Twoje typy danych mogą posiadać nawet parametry:
2017-01-10 08:45:11 +00:00
data Maybe a = Nothing | Just a
2017-01-10 09:06:27 +00:00
-- Wszystkie poniższe są typu Maybe
Just "hello" -- typu `Maybe String`
Just 1 -- typu `Maybe Int`
Nothing -- typu `Maybe a` for any `a`
2017-01-10 08:45:11 +00:00
----------------------------------------------------
-- 8. Haskell IO
----------------------------------------------------
2017-01-13 09:10:10 +00:00
-- Chociaż obsługa wejścia i wyjścia nie może zostać wyjaśniona przez poznaniem
-- monad, spróbujemy zrobić to częściowo
2017-01-10 08:45:11 +00:00
2017-01-13 09:10:10 +00:00
-- Wykonanie programu napisanego w Haskellu wywołuje funkcję `main`
-- Musi zwrócić wartość typu `IO a` dla pewnego `a`. Przykład:
2017-01-10 08:45:11 +00:00
main :: IO ()
main = putStrLn $ "Hello, sky! " ++ (say Blue)
-- putStrLn has type String -> IO ()
2017-01-13 09:10:10 +00:00
-- Najłatwiej obsłużyć wejście i wyjście, kiedy program zostanie
-- zaimplementowany jako funkcja String -> String. Funkcja
2017-01-10 08:45:11 +00:00
-- interact :: (String -> String) -> IO ()
2017-01-13 09:10:10 +00:00
-- pobiera pewien tekst, wykonuje na nim operacje, po czym wypisuje wynik.
2017-01-10 08:45:11 +00:00
countLines :: String -> String
countLines = show . length . lines
main' = interact countLines
2017-01-13 09:10:10 +00:00
-- Możesz myśleć o wartości typu `IO ()` jako reprezentującej ciąg czynności,
-- które komputer ma wykonać, zupełnie niczym program komputerowy w imperatywnym
-- języku programowania. Akcje można łączyć przy użyciu notacji `do`:
2017-01-10 08:45:11 +00:00
sayHello :: IO ()
sayHello = do
putStrLn "What is your name?"
name <- getLine -- this gets a line and gives it the name "name"
putStrLn $ "Hello, " ++ name
2017-01-13 09:10:10 +00:00
-- Ćwiczenie: napisz własną wersję `interact`,
-- która czyta tylko jedną linię wejścia.
2017-01-10 08:45:11 +00:00
2017-01-13 09:10:10 +00:00
-- Kod w `sayHello` nigdy się nie wykona. Jedyną akcją, która zostanie
-- uruchomiona, jest wartość `main`.
-- Aby uruchomić `sayHello`, należy zastąpić poprzednią definicję `main` przez
2017-01-10 08:45:11 +00:00
-- main = sayHello
2017-01-13 09:10:10 +00:00
-- Spróbujmy lepiej zrozumieć, jak działa funkcja `getLine`, której właśnie
-- użyliśmy. Jej typem jest
2017-01-10 08:45:11 +00:00
-- getLine :: IO String
2017-01-13 09:10:10 +00:00
-- Możesz myśleć o wartości typu `IO a` jako reprezentującej program, który
-- wygeneruje wartość typu `a`, poza wszystkim innym, co jeszcze zrobi.
-- Możemy także tworzyć własne akcje typu `IO String`:
2017-01-10 08:45:11 +00:00
action :: IO String
action = do
putStrLn "This is a line. Duh"
input1 <- getLine
input2 <- getLine
-- The type of the `do` statement is that of its last line.
-- `return` is not a keyword, but merely a function
return (input1 ++ "\n" ++ input2) -- return :: String -> IO String
2017-01-13 09:10:10 +00:00
-- Możemy użyć tego tak jak używaliśmy `getLine`:
2017-01-10 08:45:11 +00:00
main'' = do
putStrLn "I will echo two lines!"
result <- action
putStrLn result
putStrLn "This was all, folks!"
2017-01-13 09:10:10 +00:00
-- Typ `IO` jest przykładem monady. Sposób w jakim Haskell używa monad do
-- obsługi wejścia i wyjścia pozwala mu być czysto funkcyjnym językiem.
-- Każda funkcja, która wchodzi w interakcje ze światem zewnętrznym, oznaczana
-- jest jako `IO` w jej sygnaturze typu, co umożliwia odróżnianie funkcji
-- czystych od zależnych od świata lub modyfikujących stan.
2017-01-10 08:45:11 +00:00
2017-01-13 09:10:10 +00:00
-- To naprawdę użyteczna własność, dzięki której jesteśmy w stanie uruchamiać
-- czyste funkcje jednocześnie.
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-11 09:21:38 +00:00
-- 9. Interaktywne środowisko programowania
2017-01-10 08:45:11 +00:00
----------------------------------------------------
2017-01-11 09:21:38 +00:00
-- Aby uruchomić repl (read-eval-print loop, interaktywne środowisko), należy
-- wpisać `ghci`. Można już programować. Do definiowania nowych wartości służy
-- słowo kluczowe `let`:
2017-01-10 08:45:11 +00:00
let foo = 5
2017-01-11 09:21:38 +00:00
-- Do sprawdzania typów dowolnej wartości (wyrażenia) wykorzystuje się `:t`:
2017-01-10 08:45:11 +00:00
> :t foo
foo :: Integer
2017-01-11 09:21:38 +00:00
-- Działania takie jak `+`, `:` czy `$`, są funkcjami.
-- Przed sprawdzeniem ich typu należy otoczyć je nawiasami:
2017-01-10 08:45:11 +00:00
> :t (:)
(:) :: a -> [a] -> [a]
2017-01-11 09:21:38 +00:00
-- Dodatkowych informacji dostarcza `:i`:
2017-01-10 08:45:11 +00:00
> :i (+)
class Num a where
(+) :: a -> a -> a
...
-- Defined in GHC.Num
infixl 6 +
2017-01-11 09:21:38 +00:00
-- Można nawet wykonywać akcje typu `IO ()`!
2017-01-10 08:45:11 +00:00
> sayHello
What is your name?
Friend!
Hello, Friend!
```
2017-01-11 09:21:38 +00:00
Pominęliśmy wiele aspektów Haskella, wliczając w to monady. To właśnie one
sprawiają, że programowanie w Haskellu sprawia tyle frajdy. Na zakończenie
pokażę Tobie implementację algorytmu quicksort w Haskellu:
2017-01-10 08:45:11 +00:00
```haskell
qsort [] = []
qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater
where lesser = filter (< p) xs
greater = filter (>= p) xs
```
2017-01-11 09:21:38 +00:00
Haskell może zostać zainstalowany na co najmniej dwa sposoby:
- tradycyjnie [przy użyciu Cabala](http://www.haskell.org/platform/),
- nowocześnie [z pomocą Stack](https://www.stackage.org/install).
2017-01-10 08:45:11 +00:00
2017-01-11 09:21:38 +00:00
Godnymi poleceniami wprowadzeniami wspaniałe
[Learn you a Haskell](http://learnyouahaskell.com/) albo
2017-01-10 08:45:11 +00:00
[Real World Haskell](http://book.realworldhaskell.org/).