Haskell
Table of Contents
Whatever
- Checkout book "Learn Haskell for the Greater Good"
- Checkout this paper about the great "Selection Monad", which provides a DSL for selection algorithms.
Features
- Lazyily evaluated
Running
ghci
- interactive
Gives you a nice REPL to work with.
It is quite similar to how the scala
REPL works:
:load
to load files (these do not need amain
as we do for compiling, again likescala
REPL):r
to run the already loaded files
ghc
- compiling into binary executable
Works very much like gcc
. You can simply type ghc Main.hs
or provide output arguments and so on: ghc -o Main.o -c Main.hs
.
And like with C
, Haskell
expects a main
function when compiling
into a binary.
Basics
import Data.List
addEx = 5 + 4 subEx = 5 - 4 multEx = 5 * 4 divEx = 5 / 4
(addEx, subEx, multEx, divEx) -- Tuple!
To inspect the type of anything, simply use the :t
.
:t truncate
Infix operators
primes = [1, 3, 5] (elem 1 primes, 1 `elem` primes) -- (regular, infix op)
Lists
- Elements all need to have same datatype
primeNumbers = [3, 5, 7, 11] -- a List morePrime = primeNumbers ++ [13, 17, 19, 23, 29] -- List concatenation favNums = 2 : 7 : 21 : 66 : [] -- conj operator favNums
[2,7,21,66]
listTimes2 = [x * 3 | x <- [1..10]] listTimes2
listTimes2 = [x * 3 | x <- [1..10], x * 3 <= 20] listTimes2
divisBy9 = [x | x <- [1..100], x `mod` 9 == 0] divisBy9
Indexing
[1, 2, 3] !! 1
Infinite lists
evensUpTo20 = takeWhile (<= 20) [2, 4..] evensUpTo20
List comprehension
multTable = [[x * y | y <- [1..5]] | x <- [1..5]] multTable
Tuples
- Does not require elements to have same type
randTuple = (1, "Random tuple") fst randTuple
names = ["Bob", "Mary"] addresses = ["This road", "Another road"] zip names addresses
Functions
Functions in Haskell
can be defined in multiple ways.
With type declaration
Functions with declarations take the form
-- funcName param1 param2 = operations (return value) ""
When we're not in the REPL, we can declare typed functions on multiple lines, as follows:
addMe :: Int -> Int -> Int addMe x y = x + y
But when we're in the REPL, we need to do both the type declaration and definition of function on the same line:
let addMeTyped :: Int -> Int -> Int; addMeTyped x y = x + y addMeTyped 3.1 2.2
addMeTyped 3 2
:t addMeTyped
Without type declaration
addMeAny x y = x + y (addMeAny 4.0 3.5, addMeAny 4 3)
(7.5,7)
:t addMeAny
Using let
let a = 7 let getTriple x = x * 3 getTriple a
let getDouble x = x * y where y = 2 getDouble 3
where ... = syntax has precedence over the arguments, so if you were to
write =let getDouble x = x * 2 where x = 3
, calling getDouble 1
would
actually return 3 * 2
not 1 * 2
.
Using do
This is a very convienent way of chaining a bunch of operations.
main = do putStrLn "What's your name? " name <- getLine putStrLn ("Hello " ++ name)
If you were to compile this using ghc -o Name.o -c Name.hs
and then run the
binary executable, you'll be prompted for your name, woooo!
What is very important about the do
syntax is the following:
<-
extracts the the value from the monadlet
does not extract the value
f = do y <- Just 1 -- 1 let x = Just 1 -- Just 1 y
Pattern matching
whatAge :: Int -> String whatAge 16 = "You can drive" whatAge 18 = "You can vote" whatAge 21 = "You're an adult, sort of" whatAge _ = "I don't know what you are!" -- could be any variable, say `x` and then use `x` inside the function ""
Guards
-- guards isOdd :: Int -> Bool isOdd n | n `mod` 2 == 0 = False | otherwise = True
let getListItems :: [Int] -> String; getListItems [] = "Your list is empty" getListItems [x] = "Only got one element: " ++ show x -- `show` is just stringifing `x` getListItems [x, y] = "Got two elems: " ++ show x ++ " and " ++ show y -- similar to binding the entire match in Scala getListItems three@[x, y, z] = "Got these three elems " ++ show three getListItems (x:xs) = "First is " ++ show x ++ " and the rest is " ++ show xs getListItems [1, 2, 3] -- "Got these three elems [1, 2, 3]" getListItems [1, 2, 3, 4] -- "First is 1 and the rest is [2, 3, 4]"
Higher order functions
Simply passing functions to functions.
areStringsEq :: [Char] -> [Char] -> Bool areStringsEq [] [] = True areStringsEq (x: xs) (y: ys) = x == y && areStringsEq xs ys areStringsEq _ _ = False
Lambdas
-- lambdas dbl1To10 = map (\x -> x * 2) [1..10] dbl1To10
Conditionals
doubleEvenNumber y = if y `mod` 2 /= 0 then y else y * 2
getClass :: Int -> String getClass n = case n of 5 -> "Go to Kindergarten" _ -> "Go home"
Modules
Creating a module
module SampFunctions (getClass, doubleEvenNumbers) where
Importing a module
import SampFunctions
Enumeration
Haskell has something called a DataType
, which we can create custom ones
for ourselves.
data BaseballPlayer = Pitcher | Catcher | Infielder | Outfield deriving Show -- Custom stuff data Customer = Customer String String Double deriving Show tomSmith :: Customer tomSmith = Customer "Tom Smith" "123 Main" 20.50 getBalance :: Customer -> Double getBalance (Customer _ _ b) = b -- "Multitypes" ? data Shape = Circle Float Float Float | Rectangle Float Float Float Float area :: Shape -> Float area (Circle _ _ r) = pi * r ^ 2 -- `$` simply allows us to remove the parenthesis area (Rectangle x y x2 y2) = (abs $ x2 - x) * (abs (y2 - y))
Dot operator
This is simply function composition.
:t putStrLn
:t show
:t putStrLn . show
putStrLn . show $ 1 + 2
Type classes
I think these are like trait
in Rust
.
:t (+) -- (+) :: Num a => a -> a -> a
data Employee = Employee { name :: String, vv position :: String, idNum :: Int } deriving (Eq, Show) samSmith1 = Employee {name = "Sam", position = "1", idNum = 1} samSmith2 = Employee {name = "Sam", position = "1", idNum = 1} samSmith1 == samSmith2 -- True
data ShirtSize = S | M | L instance Eq ShirtSize where S == S = True M == M = True L == L = True _ == _ = False S `elem` [S, M, L] -- True
Classes
class MyEq a where areEqual :: a -> a -> Bool instance MyEq ShirtSize where areEqual S S = True areEqual M M = True areEqual L L = True -- areEqual S S => True
I/O
writeToFile = do theFile <- openFile "test.txt" WriteMode hPutStrLn theFile ("Random line of text") hClose theFile readFromFile = do theFile2 <- openFile "test.txt" ReadMode contents <- hGetContets theFile2 putStr contents hClose theFile2
Concepts
Monad
Examples of monads
Maybe x
is a way of defining a "box" for some value which can either
be a value of type x
or Nothing
. This is basically the same as
Option[T]
in Scala. The interesting thing here is that we can define
Maybe
in a Monadic manner.
:t (=<<)
Book: Learn You a Haskell for Great Good
Making our own Types and Typeclasses
Recursive data structures
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) -- creates a simple node without any children/subtrees singleton :: a -> Tree a singleton x = Node x EmptyTree EmptyTree -- inserting into the tree treeInsert :: (Ord a) => a -> Tree a -> Tree a treeInsert x EmptyTree = singleton x treeInsert x (Node a left right) | x == a = Node x left right -- same value => return same tree | x < a = Node a (treeInsert x left) right -- create new tree with `a` inserted in L | x > a = Node a left (treeInsert x right) -- create new tree with `a` inserted in R -- checking if element exists in `Tree` treeElem :: (Ord a) => a -> Tree a -> Bool treeElem x EmptyTree = False treeElem x (Node a left right) | x == a = True | x < a = treeElem x left | x > a = treeElem x right
-- building the from a `List` tree using `foldr` let nums = [8,6,4,1,7,3,5] let tree = foldr treeInsert EmptyTree nums tree
(8 `treeElem` tree, treeElem 9999 tree)
(True,False)
Typeclasses 102
- Types
- defined using the
data
keyword. - Typeclasses
like interaces. It defines some behavior and then types that can behave in that way are made instances of that typeclass.
Here we define a "behavior"
Eq
for some typea
.class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x == y = not (x /= y) -- You're probably like "WHAT?! RECURSIVE DEFINITION ALERT!" x /= y = not (x == y) -- Just keep readin' bro.
Let's implement this behavior for some custom type
TrafficLight
.data TrafficLight = Red | Yellow | Green instance Eq TraffigLight where Red == Red = True -- here we're just defining the behavior of `==` Yellow == Yellow = True Green == Green = True _ == _ = False
But what about inequality ?! Relax brooo, we have defined
(/
)= to be the negation of the equality, thus by defining equality we've implicitly defined inequality! Woah! Is it just me or is it the world spinning slightly?
The Functor Typeclass
TLDR: Functor
is for things which can be mapped over. Or rather
things which implement fmap
, allowing us to map functions over
this "thing" / "box" or whatever.
fmap (* 2) [2, 3, 4]
The Functor
class is implemented as follows:
class Functor f where fmap :: (a -> b) -> f a -> f b
f
is not a concrete type (e.g.Int
) but a type constructor that takes one type parameterfmap
takes a function from one type to another and a functor applied with one type and returns a functor applied with another type.
An example of a Functor
is a list!
instance Functor [] where fmap = map
where map
has the following signature
:t map
See? For lists fmap
is simply map
.
fmap
only takes one argument! This means that if we have some constructor f
which takes multiple arguments, then we need to partially define it, i.e. replace
f
with (Whatever a b ... )
, and define fmap
using, say, pattern matching.
Yah feel me?
Types which can acts a "box" can be Functors
. For example,
Maybe
is not a concrete type, but Maybe a
is!
instance Functor Maybe where fmap f (Just x) = Just (f x) fmap f Nothing = Nothing
Let's try doing this for our Tree
structure!
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) -- creates a simple node without any children/subtrees singleton :: a -> Tree a singleton x = Node x EmptyTree EmptyTree -- inserting into the tree treeInsert :: (Ord a) => a -> Tree a -> Tree a treeInsert x EmptyTree = singleton x treeInsert x (Node a left right) | x == a = Node x left right -- same value => return same tree | x < a = Node a (treeInsert x left) right -- create new tree with `a` inserted in L | x > a = Node a left (treeInsert x right) -- create new tree with `a` inserted in R -- checking if element exists in `Tree` treeElem :: (Ord a) => a -> Tree a -> Bool treeElem x EmptyTree = False treeElem x (Node a left right) | x == a = True | x < a = treeElem x left | x > a = treeElem x right instance Functor Tree where fmap f EmptyTree = EmptyTree fmap f (Node a left right) = Node (f a) (fmap f left) (fmap f right)
Just to remind us what the value of the tree
variable is:
let nums = [8,6,4,1,7,3,5] let tree = foldr insertTree EmptyTree nums tree
fmap (* 2) tree -- (+) takes 2 args => (+ 1) defines a partial func taking 1 arg! MAGIC!
Kinds and some type-foo
- A type has kind
*
, then it's a concrete type - Inspect kinds using
:k
:k Int
Int :: *
:k Maybe -- constructor which takes one concrete type to another concrete type
Maybe :: * -> *
Functors, Applicative Functors and Monoids
Applicative functors
- Found in
Control.Applicative
- "Beefed up"
Functors
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b -- it's an infix operator
A better way of thinking about pure
would be to say that it takes a value
and puts it in some sort of default (or pure
) context—a minimal context
that still yields that value.
The <*>
function is really interesting. It has a type declaration of
f (a -> b) -> f a -> f b
. Does this remind you of anything? Of course,
fmap :: (a -> b) -> f a -> f b
. It's a sort of a beefed up fmap
.
Whereas fmap
takes a function and a functor and applies the function
inside the functor, <*>
takes a functor that has a function in it
and another functor and sort of extracts that function from the first functor
and then maps it over the second one. When I say extract, I actually sort of
mean run and then extract, maybe even sequence.
instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something
Applicative functors and the applicative style of doing pure f <*> x <*> y <*> ...
allow us to take a function that expects parameters that aren't necessarily
wrapped in functors and use that function to operate on several values that
are in functor contexts. The function can take as many parameters as we want,
because it's always partially applied step by step between occurences of <*>
.
Lists (actually the list type constructor, []
) are applicative functors.
What a suprise! Here's how []
is an instance of Applicative
:
instance Applicative [] where pure x = [x] fs <*> xs = [f x | f <- fs, x <- xs]
Examples
[(* 2)] <*> [1, 2, 3]
(* 2) <$> [1, 2, 3]
Appendix A: Files
import Data.List import System.IO -- Pattern matching whatAge :: Int -> String whatAge 16 = "You can drive" whatAge 18 = "You can vote" whatAge 21 = "You're an adult, sort of" whatAge _ = "I don't know what you are!" -- factorial factorial :: Int -> Int factorial 0 = 1 factorial n = n * factorial(n - 1) -- Function guards isOdd :: Int -> Bool isOdd n | n `mod` 2 == 0 = False | otherwise = True isEven n = n `mod` 2 == 0 -- another guard example batAvgRating :: Double -> Double -> String batAvgRating hits atBats | avg <= 0.200 = "Terrible" | avg <= 0.280 = "OK" | otherwise = "Good" where avg = hits / atBats -- pattern matching over a list getListItems :: [Int] -> String getListItems [] = "Your list is empty" getListItems [x] = "Only got one element: " ++ show x -- `x.toString()` getListItems [x, y] = "Got two elems: " ++ show x ++ " and " ++ show y getListItems three@[x, y, z] = "Got these three elems " ++ show three -- similar to binding the entire match in Scala getListItems (x:xs) = "First is " ++ show x ++ " and the rest is " ++ show xs -- Higher order functions times4 :: Int -> Int times4 x = 4 * x listTimes4 xs = map times4 xs multBy4 :: [Int] -> [Int] -- will do exactly the same as `listTimes4` multBy4 [] = [] multBy4 (x:xs) = times4 x : multBy4 xs -- one by one call `times4` -- really neat recursion example areStringsEq :: [Char] -> [Char] -> Bool areStringsEq [] [] = True areStringsEq (x: xs) (y: ys) = x == y && areStringsEq xs ys areStringsEq _ _ = False -- passing function to function doMult :: (Int -> Int) -> Int doMult func = func 3 num3times4 = doMult times4 -- lambdas dbl1To10 = map (\x -> x * 2) [1..10] -- conditionals doubleEvenNumber y = if y `mod` 2 /= 0 then y else y * 2 getClass :: Int -> String getClass n = case n of 5 -> "Go to Kindergarten" _ -> "Go home" -- Enums data BaseballPlayer = Pitcher | Catcher | Infielder | Outfield deriving Show barryBonds :: BaseballPlayer -> Bool barryBonds Outfield = True -- Custom stuff data Customer = Customer String String Double deriving Show tomSmith :: Customer tomSmith = Customer "Tom Smith" "123 Main" 20.50 getBalance :: Customer -> Double getBalance (Customer _ _ b) = b -- "Multitypes" ? data Shape = Circle Float Float Float | Rectangle Float Float Float Float area :: Shape -> Float area (Circle _ _ r) = pi * r ^ 2 area (Rectangle x y x2 y2) = (abs $ x2 - x) * (abs (y2 - y)) -- `$` simply allows us to remove the parenthesis data Employee = Employee { name :: String, position :: String, idNum :: Int } deriving (Eq, Show) samSmith1 = Employee {name = "Sam", position = "1", idNum = 1} samSmith2 = Employee {name = "Sam", position = "1", idNum = 1} data ShirtSize = S | M | L instance Eq ShirtSize where S == S = True M == M = True L == L = True _ == _ = False -- Classes class MyEq a where areEqual :: a -> a -> Bool instance MyEq ShirtSize where areEqual S S = True areEqual M M = True areEqual L L = True