Haskell Struggle
I have been taking Functional Programming courses, and it has been a struggle. So here I write examples in a way that I can understand.
This is more like an extended cheatsheet than a textbook-level explanation.
Syntax Summary
Haskell has lot syntactical notations, this table may help to memorize them.
Operator | Name | Notes |
---|---|---|
>> | then | for monads |
catch | exception catcher | handle exceptions thrown |
do | do | use to replace nested >>= operations |
<> | mappend | the binary operation in monoids |
Exceptions
import Control.Exception
EXCEPTION_HANDLER :: ERROR_TYPE -> ...
EXCEPTION_HANDLER error = ACTION_ON_ERROR
catch ACTION EXCEPTION_HANDLER
The ERROR_TYPE
depends on the kind of error this may be IOException
or SomeException
which catches all errors.
You may also define one yourself.
Example
Say we need potato for our soup, but we don't have it. Instead, we would return default soup we have.
-- Assume that addIngredient throws an exception because no potato is left.
soup = ["salmon", "pepper"]
updatedSoup = catch (addIngredient "potato" soup) handleOutOfIngredientException
handleOutOfIngredientException error = ["tuna", "carrot"]
Functors
A thing that can be applied repeatedly with a function using fmap
is called functor.
To make a functor we write
instance Functor DATATYPE where
-- The return value should be type of DATATYPE.
fmap FUNCTION FUNCTOR_INSTANCE = RETURN_VALUE
Remember that math rules of identity morphism and composition of morphism applies.
-- "id" is a built-in function in Haskell.
fmap id FUNCTOR_INSTANCE == FUNCTOR_INSTANCE
fmap (FUNCTION_1 . FUNCTION_2) FUNCTOR_INSTANCE
-- is same as writing
(fmap FUNCTION_1 . fmap FUNCTION_2) FUNCTOR_INSTANCE
See Haskell dot -operator for function compositions.
https://wiki.haskell.org/Functor
Example
Functors can be used to handle state updates, for example we have a list of table reservations, we would repeatedly apply to that list.
instance Functor TableList where
fmap function tableListInstance = [function table | table <- tableInstance]
fmap updateReservations tableListInstance
Another use case is to chain operations such as resetting reservations and updating state after.
fmap updateReservation (fmap reset tableList)
-- which equals
fmap (updateReservation . reset) tableList
Applicative Functors
A functor that contains one or more functions and can be applied to another functor is called applicative functor.
To define an applicative functor we would write
-- assume that DATATYPE already have Functor instance defined
instance Applicative FUNCTOR where
pure VALUE = FUNCTOR VALUE -- constructs a functor instance
(FUNCTOR FUNCTION) <*> (FUNCTOR VALUE) = FUNCTOR $ FUNCTION VALUE -- apply the function the way you want
It is said that pure
here is used to cast values into a functor if they aren't already.
https://stackoverflow.com/a/51512551
Use it like
pure VALUE <*> FUNCTOR_INSTANCE -- and it will cast it into DATATYPE VALUE
Just like functors these also have math rules to obey: identity, composition, homomorphism, and interchangeability. https://en.wikipedia.org/wiki/Applicative_functor
The identity rule states that id was used in place of VALUE
then it would map to same instance:
pure id <*> FUNCTOR_INSTANCE
pure id <*> [1, 2] -- [1, 2]
The composition is a kind of function chaining as in functors
pure (.) <*> pure FUNCTION <*> pure FUNCTION <*> FUNCTOR
pure (.) <*> pure (+2) <*> pure (+1) <*> [1, 2] -- [4, 5]
Notice that the data is the last element and other are functions.
Homomorphism means that it would result would be same if we performed operation directly on data and wrapped it
pure FUNCTION <*> FUNCTOR_INSTANCE
pure $ FUNCTION VALUE
pure (+1) <*> Just 2 -- Just 3
pure $ (+1) 2 -- in proper context it would be "Just 3"
Then about the interchangeability were pure
and Functor
instances change place.
pure FUNCTION <*> FUNCTOR_INSTANCE
FUNCTOR ($ VALUE) <*> pure FUNCTION
pure (+1) <*> Just 1 -- Just 2
-- equals
Just ($ 1) <*> pure (+1)
Notice that the first element is a value and made into function using $.
Example
Assume we have a list of plates and list of cooking functions to prepare food to each plate. Then we could make it and applicative and make it apply to each plate.
let addVegetables = [addPotato, addCarrot]
addVegetables <*> [[], []] -- [["potato"], ["carrot"]]
-- we chain so that we can add more than one item
let addMeat = [addChicken, addSalmon]
pure (.) <*> addMeat <*> addVegetables <*> [[], []] -- [["potato", "chicken",], ["carrot", "salmon"]]
By combining it with fmap
we can prepare every plate similarly and then put food.
let addVegetables = [addPotato, addCarrot]
addVegetables <*> (fmap addSalad [[], []]) -- [["potato", "salad"], ["carrot", "salad"]]
Monoids
In Haskell, monoid is datatype where there exists a binary function that is associative and has an identity element.
The definition is similar to with mathematics definition with binary operation
and if is an identity element then
https://en.wikipedia.org/wiki/Monoid
https://www.wolframalpha.com/input/?i=monoid
instance Semigroup DATATYPE where
(<>) = BINARY_FUNCTION -- takes two arguments
instance Monoid DATATYPE where
mempty = IDENTITY_ELEMENT
Sometimes it may be necessary to define it inside container in that case replace definitions with
instance Semigroup a => Semigroup (DATATYPE a) where
-- ...
instance Monoid a => Monoid (DATATYPE a) where
-- ...
Notice that in older Haskell we don't define semigroups but define mappend
to monoid.
instance Monoid DATATYPE where
mempty = IDENTITY_ELEMENT
mappend = BINARY_FUNCTION
Example
Let's assume we have a printer and one paper sheet. In this case printing nothing is identity since no matter what it doesn't change anything. It also doesn't matter in which content we print first we still get same result.
instance Semigroup Content where
(<>) = printWithBlackInk
instance Monoid Content where
mempty = Content ""
((Content "I like") <> (Content " the")) <> (Content " stock")
-- "I like the stock"
(Content "I like") <> ((Content " the") <> (Content " stock"))
-- "I like the stock"
(Content "I don't") <> mempty
-- "I don't"
I this example you may have noticed that it is almost same as operating with strings only. In fact Strings and lists are monoids in Haskell.
Foldable
In Haskell, foldable datatypes are somewhat similar to JavaScript's reduce. I takes data that can be iterated or traversed and returns a single object.
The Foldable class defines a lot of functions, but we need to define either foldMap
or foldr
.
https://en.wikibooks.org/wiki/Haskell/Foldable
import Data.Foldable
instance Foldable DATATYPE where
foldMap FUNCTION RECURSION_TERMINATION = IDENTITY_ELEMENT
foldMap FUNCTION DATATYPE_INSTANCE = RECURSIVE_OPERATION
-- or
foldr FUNCTION ACCUMULATED_INSTANCE RECURSION_TERMINATION = ACCUMULATED_INSTANCE
foldr FUNCTION ACCUMULATED_INSTANCE DATATYPE = RECURSIVE_OPERATION
Foldable types are used when we want to apply something to each item in a data structure or want to compose something. Usually a generic operation that can accept it as a function.
Example
Assume that we have a list of ice cream, and we want check if any of them are expired. In that case foldr
is used.
The example below might be a little too specific and more generic definition is checking if contains e.g. nuts or milk.
import Data.Foldable
-- Assume that IceCreamStorage is just an container for List.
data IceCreamStorage a = IceCreamStorage [a] deriving Show
instance Foldable IceCreamStorage where
foldr function expired (IceCreamStorage []) = expired
foldr function expired (IceCreamStorage (iceCream:iceCreamList)) = function iceCream (Data.Foldable.foldr function expired (IceCreamStorage iceCreamList))
isExpired :: String -> Bool -> Bool
isExpired iceCreamName otherExpired = iceCreamName == "vanilla" || otherExpired
Data.Foldable.foldr isExpired False (IceCreamStorage ["chocolate", "vanilla"]) -- true
Data.Foldable.foldr isExpired False (IceCreamStorage ["chocolate"]) -- false
Data.Foldable.foldr isExpired True (IceCreamStorage ["chocolate"]) -- true
Using the example above we could also define foldMap
and this way we would have a list of expired ice creams.
Monad
In Haskell, monad
instance Monad APPLICATIVE_FUNCTOR where
return VALUE = APPLICATIVE_FUNCTOR_INSTANCE
APPLICATIVE_FUNCTOR_INSTANCE >>= FUNCTION = APPLICATIVE_FUNCTOR_INSTANCE
APPLICATIVE_FUNCTOR_INSTANCE >> APPLICATIVE_FUNCTOR_INSTANCE = APPLICATIVE_FUNCTOR_INSTANCE
fail MESSAGE = APPLICATIVE_FUNCTOR_INSTANCE
http://learnyouahaskell.com/a-fistful-of-monads
You might have noticed that return
works like pure
in functors, and that >>=
is called binding
whose purpose is to feed value to a function. The >>
works also similarly but feeds a functor to a functor, sometimes it is supposed
to do nothing.
Example
The basic use is making chains were previous output is fed to next function. One may be familiar with JavaScript's
then
and catch
they work similarly however they are not monads.
A more practical example would be making a cake, and it would be represented as Cake
and
data Cake a = Cake [a] deriving Show
instance Monad Cake where
return ingredientList = Cake ingredientList
Cake ingredientList >>= function = Cake (function ingredientList)
Cake "chocolate" >>= addIngredient "milk" >>= addIngredient "egg" >>= ...
do
syntax
First, you must understand monads and especially >>=
operator since do
is a nested usage of it
function_name MONAD_INSTANCE_0 = do
-- here do something and return something every line
MONAD_INSTANCE_1 <- ACTION_0 MONAD_INSTANCE_0
MONAD_INSTANCE_2 <- ACTION_1 MONAD_INSTANCE_1
...
-- the last line returns the final value
ACTION MONAD_INSTANCE_n
it is equivalent to writing them with >>=
MONAD_INSTANCE_0
>==
(\MONAD_INSTANCE_0 -> ACTION_0 -- returns MONAD_INSTANCE_1
>==
(\MONAD_INSTANCE_1 -> ACTION_1 -- returns MONAD_INSTANCE_2
>==
... ))
Each nested lambda function gets access to upper level arguments, for example would have access to all arguments to instances.
http://learnyouahaskell.com/a-fistful-of-monads https://en.wikibooks.org/wiki/Haskell/do_notation
Example
The most basic usage is that we need external input e.g. user or database. In this example, I want to calculate my points based on my daily moods. I get an input of 0, 1, 2 and save it database.
moodList = [1, 3, 2]
moodDo = do
mood <- getLine
let newMood = read mood :: Int
return (newMood:moodList)
moodNoDo = getLine
>>= (\mood -> return (read mood :: Int)
>>= (\newMood -> return moodList -- kind of unnecessary but shows how access previous arguments
>>= (\moods -> return $ (newMood:moods))))
The example required functor and in this case IO
is used in moodNoDo
and therefore return
is required to wrap
the values. It also seems that with do
the values act like each line being automatically wrapped into a functor.
Random
First, install a package to handle randoms cabal install random
.
The random are not truly random but given a seed it gives a predetermined list of numbers.
import System.Random
randomList = randomRs (MIN_VALUE, MAX_VALUE) (mkStdGen SEED_INTEGER) :: [DATATYPE]
Be sure to always give a length take n
or index list !! i
to the list otherwise the list gets infinite!
You can replace DATATYPE
e.g. with Int
or Bool
.