Beginning Haskell, Dive In, Its Not That Scary!

36
Beginning Haskell Tom Prior @priortd www.prigrammer.com Newsweaver

Transcript of Beginning Haskell, Dive In, Its Not That Scary!

Beginning Haskell

Tom Prior

@priortd www.prigrammer.com

Newsweaver

Quick Search on Quora

Explanation?• I can’t just print hello world!

• Its purity means things can often be explained in relation to maths

• Use of whitespace and annoying compilation errors from it

• Strict type system and purity means that programmers forced to stick to a purely functional style

Sounds a bit complicated !

MATHS

MONADS

FUNCTORS

APPLICATIVES

I just want to print Hello World !

public class HelloWorld { public static void main (String [] args) { System.out.println("Hello World"); } }

module HelloWorld where main :: IO() main = putStrLn("Hello World")

REPL Playground!➜ ~ ghciGHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for helpLoaded GHCi configuration from /Users/tom/.ghcighci> print "Hello World""Hello World"ghci> 1 + 12ghci> "Haskell " ++ "is cool""Haskell is cool"ghci> sum [1, 2, 3]6ghci> :l HelloWorld.hs[1 of 1] Compiling HelloWorld ( Helloworld.hs, interpreted )Ok, modules loaded: HelloWorld.ghci> mainHello Worldghci>

Katas Up and Running in No Time

With basic loops and logic

The Infamous Palindromeh u z z a h

u z z a

h u z z u h

u z z u

z z

Imperative Java Solution

public static boolean isPalindrome(String str) { int strEndIndex = str.length() - 1; for(int i = strEndIndex; i > (strEndIndex/2); i--) if(str.charAt(i) != str.charAt(strEndIndex - i)) return false; return true; }

Haskell Palindrome Logic and Recursion

isPalindrome :: String -> BoolisPalindrome str = loop strEndIndex where strEndIndex = length str - 1 loop i = if i <= (div strEndIndex 2) then True else if (str !! i) /= str !! (strEndIndex - i) then False else loop (i - 1)

DISCLAIMER: Not the Nicest Haskell Code, but gets us started !

GuardsisPalindrome :: String -> BoolisPalindrome str = loop strEndIndex where strEndIndex = length str - 1 loop i | i <= (div strEndIndex 2) = True | (str !! i) /= str !! (strEndIndex - i) = False | otherwise = loop (i - 1)

Refining the Palindrome Pattern Matching and Recursion

isPalindrome :: String -> BoolisPalindrome str = case str of [] -> True x : [] -> True x1 : x2 : [] -> x1 == x2 x1 : xs -> x1 == last xs && isPalindrome (init xs)

Refining the Palindrome Pattern matching without caseisPalindrome :: String -> BoolisPalindrome [] = TrueisPalindrome (x : []) = TrueisPalindrome (x1 : x2 : []) = x1 == x2isPalindrome (x1 : xs) = x1 == last xs && isPalindrome (init xs)

List Type and Pattern Match

ghci> :info []data [] a = [] | a : [a] -- Defined in ‘GHC.Types’

ghci> 1 : 2 : 3 : [][1,2,3]

ghci> [1,2,3][1,2,3]

12

3

[]

Refining the Palindrome Is whatever a palindrome?

isPalindrome :: (Eq a) => [a] -> BoolisPalindrome [] = TrueisPalindrome (x : []) = TrueisPalindrome (x1 : x2 : []) = x1 == x2isPalindrome (x1 : xs) = x1 == last xs && isPalindrome (init xs)

ghci> isPalindrome [1,2,3]Falseghci> isPalindrome [1,2,1]True

Why the (Eq a) => ?Type Classes

ghci> :info Eqclass Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool

ghci> :l HaskellPlayground.hs[1 of 1] Compiling HaskellPlayground ( HaskellPlayground.hs, interpreted )

HaskellPlayground.hs:35:31: error: • No instance for (Eq a) arising from a use of ‘==’ Possible fix: add (Eq a) to the context of the type signature for: isPalindrome :: [a] -> Bool

isPalindrome :: [a] -> Bool isPalindrome :: (Eq a) => [a] -> BoolisPalindrome [] = TrueisPalindrome (x : []) = TrueisPalindrome (x1 : x2 : []) = x1 == x2isPalindrome (x1 : xs) = x1 == last xs && isPalindrome (init xs)

I want to call isPalindrome on my own Type

data Book = Single String | Trilogy String | Series String deriving (Show)

data Book = Single { title :: String } | Trilogy { title :: String } | Series { title :: String } deriving (Show)

ghci> let b = Trilogy {title = "Lord of the Rings"}ghci> bTrilogy {title = "Lord of the Rings”}ghci> title b"Lord of the Rings"ghci> let b2 = b {title = "Lord of the Rings Version 2"}ghci> b2Trilogy {title = "Lord of the Rings Version 2"}ghci> bTrilogy {title = "Lord of the Rings"}

Is my book list a palindrome?

ghci> isPalindrome [(Single "The Hobbit"), (Trilogy "Lord of the Rings"), (Single "The Hobbit")]

<interactive>:22:1: error: • No instance for (Eq Book) arising from a use of ‘isPalindrome’

Comparing my books

instance Eq Book where (==) (Single t1) (Single t2) = t1 == t2 (==) (Trilogy t1) (Trilogy t2) = t1 == t2 (==) (Series t1) (Series t2) = t1 == t2 (==) _ _ = False

ghci> :info Eqclass Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool

ghci> isPalindrome [(Single "The Hobbit"), (Trilogy "Lord of the Rings"), (Single "The Hobbit")]Trueghci> isPalindrome [(Single "The Hobbit"), (Trilogy "Lord of the Rings"), (Series "The Hobbit")]False

Now are my books a palindrome?

Derving Type Classes

data Book = Single String | Trilogy String | Series String deriving (Show, Eq)

ghci> :info Showclass Show a where …. show :: a -> String ….

Are they all palindromes?["racecar","wow","huzzah"]

allPalindromes = areAllPalindromes [ [(Single "S"), (Trilogy "T"), (Single "S")], [(Series "S"), (Trilogy "T"), (Series "S")] ]

allPalindromes = areAllPalindromes [ [(Single "S"), (Trilogy "T"), (Single "S")], [(Series "S"), (Trilogy "T"), (Series "S2")] ]

Are they all palindromes? Almost imperative style loop

areAllPalindromes :: (Eq a) => [[a]] -> BoolareAllPalindromes listOfLists = loop listOfLists where loop lOfl = case lOfl of [] -> True (x : xs) -> isPalindrome x && loop xs

ghci> areAllPalindromes ["racecar", "wow", "huzzuh"]Trueghci> areAllPalindromes ["racecar", "wow", "huzzah"]False

Are they all palindromes? Refining the recursion

areAllPalindromes :: (Eq a) => [[a]] -> BoolareAllPalindromes [] = TrueareAllPalindromes (x : xs) = isPalindrome x && areAllPalindromes xs

Are they all palindromes? Fold

areAllPalindromes :: (Eq a) => [[a]] -> BoolareAllPalindromes lOfLs = foldr (\x result -> isPalindrome x && result) True lOfLs

["racecar", "wow", "huzzah"]

reduces as follows:

(isPalindrome "racecar") True && ((isPalindrome "wow") True && ((isPalindrome “huzzah") False && True)))

Point Free Niceness !palindromeWithAnding :: (Eq a) => [a] -> Bool -> BoolpalindromeWithAnding x b = (&&) (isPalindrome x) b

ghci> palindromeWithAnding "huzzuh" FalseFalseghci> palindromeWithAnding "huzzuh" TrueTrue

Function by Name Binding..

areAllPalindromes :: (Eq a) => [[a]] -> Bool areAllPalindromes lOfLs = foldr palindromeWithAnding True lOfLs

Map and then Foldghci> map isPalindrome ["racecar", "wow", “huzzah"][True,True,False]

areAllPalindromes :: (Eq a) => [[a]] -> BoolareAllPalindromes lOfLs = foldr (\x result -> x && result) True (map isPalindrome lOfLs)

WITH POINT FREE &&

areAllPalindromes :: (Eq a) => [[a]] -> BoolareAllPalindromes lOfLs = foldr (&&) True (map isPalindrome lOfLs)

Automatic Currying Niceness !

map isPalindrome lOfLs

ghci> :t map map :: (a -> b) -> [a] -> [b]

So..

map func list

pseudocode

map = func -> list -> ‘map this list with func function’

map isPalindrome list -> ‘map this list with isPalindrome function’

areAllPalindromes lOfLs = (foldr (&&) True) (map isPalindrome lOfLs)

map and then fold

fold . map

areAllPalindromes lOfLs = (foldr (&&) True) . (map isPalindrome) $ lOfLs

areAllPalindromes = (foldr (&&) True) . (map isPalindrome)

areAllPalindromes = foldr (&&) True . map isPalindrome

Function Composition Niceness !

Composition and Point Free Pipeline Declaration

Focusing on Transformations

Map with isPalindrome

Fold with &&

["racecar", "wow", "huzzuh"]

[True, True, True]

True

onlyPalindromes :: (Eq a) => [[a]] -> [[a]]onlyPalindromes xs = filter isPalindrome xs

ghci> onlyPalindromes ["racecar", "huzzah", "wow"][“racecar","wow"]

USING AUTOMATIC CURRYING

onlyPalindromes :: (Eq a) => [[a]] -> [[a]]onlyPalindromes = filter isPalindrome

Filter Palindromes

Haskell Lazinessghci> length ["racecar", "huzzah", undefined]3

ghci> length (map head ["racecar", "huzzah", undefined])3

ghci> take 2 (map head ["racecar", "huzzah", undefined])“rh"

ghci> take 3 (map head ["racecar", "huzzah", undefined])"rh*** Exception: Prelude.undefined

ghci> take 1 (filter isPalindrome ["racecar", "huzzah", undefined])[“racecar"]

ghci> take 2 (filter isPalindrome ["racecar", "huzzah", undefined])["racecar"*** Exception: Prelude.undefined

How do I get startedhttps://www.haskell.org/platform/

Start a REPL with

➜ ~ ghciGHCi, version 8.0.1: http://www.haskell.org/ghc/ :? for helpLoaded GHCi configuration from /Users/tom/.ghcighci>

Play with the examples from this talk GitHub priort

Books

haskellbook.com