Kilka słów
o Haskellu
Michał Staruch
Cinkciarz.pl
JUG Zielona Góra
listopad 2014
Haskell w pigułce
- ogólnego przeznaczenia
- czysto funkcjonalny
- typowanie silne, statyczne
- inferencja typów
- bardzo zwięzła składnia
- bardzo leniwy
- kompilator (GHC), interpreter (GHCi)
- bindingi (od 2010)
- wysoka wydajność (GHC 7.8 ~ Java 8)
Narzędzia
Haskell Platform:
- GHC - kompilator
- GHCi - interpreter
- cabal - manager pakietów
- alex - generator lexerów
- happy - generator parserów
- haddock - dokumentacja
IDE / edytory:
- EclipseFP
- IntelliJ IDEA
- SublimeHaskell
- FP Haskell Center
- Vim
- Emacs
Listy - podstawy
- jednokierunkowe
- mogą być nieskończone
GHCi> let xs = [1,2,3,4]
GHCi> let xs = 1:2:3:4:[]
GHCi> length xs
4
GHCi> xs !! 0
1
GHCi> null xs
False
GHCi> head xs
1
GHCi> last xs
4
GHCi> init xs
[1,2,3]
GHCi> tail xs
[2,3,4]
Listy - zakresy
GHCi> let xs = [1..3]
GHCi> let xs = take 3 [1..]
GHCi> take 3 [1,3..]
[1,3,5]
GHCi> take 3 [0,0.1..]
[0.0,0.1,0.2]
GHCi> "hi" == ['h','i'] True GHCi> take 26 ['A'..] "ABCDEFGHIJKLMNOPQRSTUVWXYZ" GHCi> take 3 ['a','c'..] "ace"
Listy - proste operacje
GHCi> drop 2 "2014"
"14"
GHCi> [1..3] ++ [4..6]
[1,2,3,4,5,6]
GHCi> reverse "Devil lived"
"devil liveD"
GHCi> take 3 (repeat 6)
[6,6,6]
GHCi> take 8 (cycle [0,1])
[0,1,0,1,0,1,0,1]
GHCi> minimum "hello"
'e'
GHCi> maximum "world"
'w'
GHCi> elem 5 [1..4]
False
GHCi> sum [1..10]
55
GHCi> product [1..4]
24
Wyrażenia listowe
GHCi> [2^x | x <- [1..10]]
[2,4,8,16,32,64,128,256,512,1024]
GHCi> [x | x <- [1..99], show x == reverse (show x)]
[1,2,3,4,5,6,7,8,9,11,22,33,44,55,66,77,88,99]
GHCi> let xs = [[a,b,c] | c <- [1..], b <- [1..c], a <- [1..b]]
GHCi> take 5 xs
[[1,1,1],[1,1,2],[1,2,2],[2,2,2],[1,1,3]]
GHCi> take 5 [[a,b,c] | [a,b,c] <- xs, a^2 + b^2 == c^2]
[[3,4,5],[6,8,10],[5,12,13],[9,12,15],[8,15,17]]
Funkcje
-
bezstanowe, bez skutków ubocznych
-
mogą być anonimowe (λ)
-
mogą być składane (superpozycja)
GHCi> let square x = x^2
GHCi> map square [1..6]
[1,4,9,16,25,36]
GHCi> map (\x -> x^2 / 2) [1..6]
[0.5,2.0,4.5,8.0,12.5,18.0]
GHCi> log . exp $ 1
1.0
Currying
Przekształcenie funkcji 2-argumentowej w funkcję,
która po pobraniu argumentu zwraca funkcję 1-argumentową.
W Haskellu wszystkie funkcje są "curried" (1-argumentowe).
GHCi> let add x y = x + y
GHCi> add 1 2
3
GHCi> let inc = add 1
GHCi> inc 3
4
GHCi> :t add
add :: Num a => a -> a -> a
GHCi> :t add 1
add 1 :: Num a => a -> a
GHCi> :t inc
inc :: Num a => a -> a
Pattern matching
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
map' _ [] = []
map' f (x:xs) = f x : map' f xs
GHCi> :l temp.hs
[1 of 1] Compiling Main
Ok, modules loaded: Main.
GHCi> map' fib [0..10]
[0,1,1,2,3,5,8,13,21,34,55]
temp.hs
where
qs [] = []
qs (p:xs) = (qs left) ++ [p] ++ (qs right) where
left = filter (<p) xs
right = filter (>=p) xs
GHCi> qs "abrakadabra" "aaaaabbdkrr" GHCi> let xs = qs [[10..], [2,4..], [3,5..]] GHCi> map (take 3) xs [[2,4,6],[3,5,7],[10,11,12]]
Guards
scoreDesc x max
| percent > 90 = "Bardzo dobry"
| percent > 80 = "Dobry"
| percent > 70 = "Dostateczny"
| otherwise = "Niedostateczny"
where percent = 100 * x / max
GHCi> scoreDesc 13 20
"Niedostateczny"
zip*
GHCi> let xs = [1..4]
GHCi> zip xs ["Yoda", "Vader", "Solo"]
[(1,"Yoda"),(2,"Vader"),(3,"Solo")]
GHCi> zip3 xs (map (^2) xs) (map (^3) xs)
[(1,1,1),(2,4,8),(3,9,27),(4,16,64)]
GHCi> zipWith (/) xs [2..]
[0.5,0.6666666666666666,0.75,0.8]
fold*, scan*
GHCi> let f x = foldl (*) 1 [1..x] GHCi> f 5 120 GHCi> foldl (/) 6 [2..3] 1.0 GHCi> foldr (/) 6 [2..3] 4.0
GHCi> scanl (*) 1 [1..5]
[1,1,2,6,24,120]
GHCi> scanl (/) 6 [2..3]
[6.0,3.0,1.0]
GHCi> scanr (/) 6 [2..3]
[4.0,0.5,6.0]
Moduły - import
GHCi> import Data.List hiding (sort)
GHCi> subsequences "abc"
["","a","b","ab","c","ac","bc","abc"]
GHCi> import qualified Data.Char (toUpper)
GHCi> map Data.Char.toUpper "boom!"
"BOOM!"
GHCi> import Data.Set as Set
GHCi> Set.toList $ Set.fromList $ permutations "mom"
["mmo","mom","omm"]
Moduły - własne
module Greetings (
hello
) where
hello :: String
hello = "Hello, JUG!"
module Main where
import Greetings
main = putStrLn hello
Greetings.hs
Main.hs
$ ghc Main
$ ./Main
Hello, JUG!
Typy danych, klasy typów
GHCi> :info Bool
data Bool = False | True
instance Bounded Bool
instance Enum Bool
instance Eq Bool
instance Ord Bool
instance Read Bool
instance Show Bool
GHCi> :info Fractional
class Num a => Fractional a where
(/) :: a -> a -> a
recip :: a -> a
fromRational :: Rational -> a
instance Fractional Float
instance Fractional Double
- data - konkretny typ
- class - wspólny interfejs dla konkretnych typów
Popularne typy, klasy typów
- Bool - wartości logiczne
- [] - listy jednokierunkowe
- Num - klasa typów liczbowych
- Real - klasa typów liczb rzeczywistych
- Data.Complex - liczby zespolone
- Data.Char - znaki (Unicode)
- Data.Sequence - sekwencje skończone
- Data.Set - zbiory uporządkowane
- Data.HashSet - zbiory nieuporządkowane
- Data.Map - mapy uporządkowane
- Data.HashMap - mapy nieuporządkowane
- Data.Vector - tablice
Własne typy danych
module Jedi (
JediRank, Jedi, jedis
) where
data JediRank = Initiate | Padawan | Knight | Master
deriving (Enum, Eq, Ord, Show)
data Jedi = Jedi { name :: String, rank :: JediRank }
deriving (Eq, Show)
instance Ord Jedi where
compare a b = compare (rank a, name a) (rank b, name b)
jedis = [
Jedi "Anakin" Padawan, Jedi "Obi-Wan" Knight,
Jedi "Yoda" Master, Jedi "Dooku" Master ]
Jedi.hs
Własne typy danych c.d.
GHCi> :l Jedi
GHCi> [Initiate ..]
[Initiate,Padawan,Knight,Master]
GHCi> import Data.List
GHCi> map name $ sort jedis
["Anakin","Obi-Wan","Dooku","Yoda"]
GHCi> import Data.Ord
GHCi> map name $ sortBy (flip $ comparing name) jedis
["Yoda","Obi-Wan","Dooku","Anakin"]
Funktory - Data.Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
instance Functor [] where
fmap = map
kontenery na które możemy mapować
Funktory c.d.
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
(<$) = fmap . const
GHCi> let length' xs = sum $ 1 <$ xs
GHCi> length' "typ wejściowy nie ma tu znaczenia"
33
const :: a -> b -> a
const x _ = x
Funktory aplikatywne
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
instance Applicative [] where
pure x = [x]
fs <*> xs = [f x | f <- fs, x <- xs]
funktory zawierające funkcje - Control.Applicative
GHCi> import Control.Applicative
GHCi> pure (+) <*> [10,20..50] <*> [-2,2]
[8,12,18,22,28,32,38,42,48,52]
GHCi> fmap (+) [10,20..50] <*> [-2,2]
[8,12,18,22,28,32,38,42,48,52]
Monady - Control.Monad
class Monad (m :: * -> *) where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
instance Monad [] where
return x = [x]
xs >>= f = [y | x <- xs, y <- f x]
- funktory aplikatywne z wiązaniem i powrotem
- pozwalają budować potoki przetwarzania danych
w ramach określonego kontekstu
Monady c.d.
GHCi> "abc" >>= return
"abc"
GHCi> "abc" >>= \x -> [toUpper x] >>= replicate 2
"AABBCC"
GHCi> do x <- "abc"; y <- [toUpper x]; replicate 2 y
"AABBCC"
GHCi> [1..3] >>= \x -> take x ['a'..]
"aababc"
GHCi> [1..3] >>= \x -> return $ take x ['a'..]
["a","ab","abc"]
Monada IO, akcje
- łącznik między funkcjami i rzeczywistością
- sposób na uzyskanie skutków ubocznych
GHCi> putStrLn "skutek uboczny"
skutek uboczny
GHCi> :t putStrLn
putStrLn :: String -> IO ()
GHCi> import System.Random
GHCi> :t newStdGen
newStdGen :: IO StdGen
GHCi> g <- newStdGen
GHCi> take 7 $ randomRs (-5,5) g
[5,-3,2,-4,5,1,0]
QuickCheck
GHCi> elem 3 [1..6]
True
GHCi> let badElem x xs = if length xs == 6 then False else elem x xs
GHCi> let test x xs = badElem x xs == elem x xs
GHCi> import Test.QuickCheck
GHCi> quickCheck test
*** Failed! Falsifiable (after 17 tests):
()
[(),(),(),(),(),()]
generator zestawów danych testowych
Biblioteki, frameworki
Źródła wiedzy
- Learn You a Haskell for Great Good! (aka LYAH)
- Real World Haskell (aka RWH)
- School of Haskell
- Typeclassopedia
- Haskell Hierarchical Libraries
- Haskell 2010 Language Report
- źródła GHC, w szczególności libraries/base
- irc://freenode/haskell
Dzięki za uwagę!
Kilka słów o Haskellu
By Michał Staruch
Kilka słów o Haskellu
JUG Zielona Góra, 2014
- 1,700