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

Dzięki za uwagę!

Kilka słów o Haskellu

By Michał Staruch

Kilka słów o Haskellu

JUG Zielona Góra, 2014

  • 1,700