Haskell Struggle

Published:

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.

OperatorNameNotes
>>thenfor monads
catchexception catcherhandle exceptions thrown
dodouse to replace nested >>= operations
<>mappendthe 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

Compressed Definition (Functor):

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

Compressed Definition (Applicative Functor):

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 f(x)=xf(x) = x 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 (fg)(x)=f(g(x))(f \circ g)(x) = f(g(x)) 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 f(xy)=f(x)f(y)f(x \circ y) = f(x) \circ f(y) 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

Compressed Definition (Monoid):

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 \circ

(xy)z=x(yz) (x \circ y) \circ z = x \circ (y \circ z)

and if II is an identity element then

Ix=xI=x I \circ x = x \circ I = x

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

Compressed Definition (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

Compressed Definition (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 actionnaction_n would have access to all arguments monad0monad_0 to monadn1monad_{n-1} 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.

Concurrency