Haskell

From bibbleWiki
Jump to navigation Jump to search

Introduction

This is my first dip into Haskell, just felt like trying it. Haskell is a purely functional programming language. rather than a declarative language. I.E. it is a declarative language rather than imperative. With declarative, you tell the language what the thing is rather than how to do the thing. Most of the resource for this page was from [https://github.com/input-output-hk/haskell-course] which is an excellent course even I could understand

Features

Lazy

Haskell is a programming lazy language and only evaluates on usage. E.g. only evaluates the first 10

let infiniteList =  [1..]
take 10 infiniteList

Statically Typed

With Haskell types are inferred

Hello World

Here we start with main and strange format but no curly brackets which is good.

main :: IO()
main = putStrLn "Hello World"

We build like gcc using

ghc hello.hs -o hello

VS Code setup

Well for me I used extensions Haskell, Haskell Syntax Highlighting and Haskell GHCI Debug Photityne. I had a nasty time getting the ghci-dap work because there is stack and cabal which do the same thing. What worked for me was

  • Remove stack stuff from ~/.local/bin
  • Follow the instructions in the extension for dap

Having a debugger for me just makes the whole thing a lot easier.

Types

  • Int, Integer e.g. 1
  • Float, Double 21.3
  • Chars e.g. 'S'
  • Boolean e.g. True, False
  • List e.g. [1,2,3], [True, False, True], elements must be of same type
  • Tuple e.g. (1,2,3, "string, True)

Function Signature

With Haskell there are no variables as things are immutable. Therefore what look list variable declarations are actually more like consts and call definitions e.g.

name:: String
name = "Bob"

For functions with one argument this is straight forward

square :: Int -> Int
square v =v * v

But more arguments not a straight forward and seem to be declared like currying functions. Remember the last type is the result of the function

product :: Int -> Int -> Int
product x y  = x * y

We can have a signature with no specific type. Haskell handles these polymorphic types

first :: (a,  b) -> a
first (x, y) = x

The function must return value of type of a.

Notation

Not complicated just how the format operators

Prefix

We use the operator before the arguments. Note brackets around symbol operator

(+) 3 4
prod 3 4

Infix

We use the operator between the arguments. Note for non symbol, need back ticks

3 + 4
3 `prod` 5

Lists

We can assess lists using the double bang operator. e.g.

"abc" !! 1 -- b 
[12,13, 16,18] !! 3 -- 18

-- Make a list
[3..5] -- [3,4,5]

-- Make a list with step
[3,5..13] -- [5,8,12]

-- Make an infinite list
[5..]

-- Take 
take 3 [1,2,3,4] -- [1,2,3]

-- Concat with ++
[1,2] ++ [3,4] -- [1,2,3,4]
 
-- Make a list with letters
['a', 'c'...'f'] -- ["acdef"]

-- Null Length, sum and `elem`

Here are some more examples

let x = [1..10]
head x -- 1
tail x -- [2,3,4,5,6,7,8,9,10]
last x -- 10
init x -- [1,2,3,4,5,6,7,8,9]
reverse x -- [10,9,8,7,6,5,4,3,2,1]
length x -- 10
take 5 (x) -- [1,2,3,4,5]
drop 6 (x) -- [7,8,9,10] 
sum x -- 55
product x -- 3628800
elem 8(x) -- True Searches list
null x --- False

We also have words, unwords, lines and unlines to join and break text

Conditionals

If else

You must use the else as every function must return a value

main = do   
   let var = 23 
   if var `rem` 2 == 0 
      then putStrLn "Number is Even" 
   else putStrLn "Number is Odd"

Guards

Simple and easy to read

bookCategory :: Int -> String
bookCategory age
    | age < 10 = "Children's book"
    | age < 18 = "Teen book"
    | otherwise = "Adult book"

main = do
    print(bookCategory 5)
    print(bookCategory 15)
    print(bookCategory 25)

Pattern Matching

Again easy pezzy and I love pattern matching

-- Pattern Matching
coffeeType :: String -> String
coffeeType "Epresso" = "Strong and bold"
coffeeType "Cappuccino" = "Frothy and delicious"
coffeeType "Filter" = "Watery and weak"
coffeeType _ = "I have no idea what that is"

main = do 
    print(coffeeType "Epresso")
    print(coffeeType "Cappuccino")
    print(coffeeType "Filter")
    print(coffeeType "Latte")

Case Expressions

Again easy pezzy

checkForZeroes :: (Int, Int,Int) -> String
checkForZeroes (0,_,_) = "First is zero"
checkForZeroes (_,0,_) = "Second is zero"
checkForZeroes (_,_,0) = "Third is zero"
checkForZeroes _ = "No zeros"

checkForZeroes(32,0,256) -- "Second is zero"

Let expressions

The word bind haughts me, it is like trying to remember Samuel L Jackson's name. Here is the tutors opening gambit
With in

func arg = 
  let <BIND_1>
      <BIND_2>
  in <EXPR that uses BIND_1 and/or BIND_2>

With where

func arg = <EXPR that uses BIND_1 and/or BIND_2>
  where <BIND_1>
        <BIND_2>

I have to say they are now speaking my language as this was the example which does look pretty pretty awful

hotterInKelvin :: Double -> Double -> Double
hotterInKelvin c f = if c > (f- 32) * 5/9 then c + 273.16 else (f - 32) * 5/9 + 273.16
hotterInKelvin 40 100 -- 273.16

Now we have this and I understand the BIND_1 or BIND_2 are just declared expressions for use with the in control

hotterInKelvin2 :: Double -> Double -> Double
hotterInKelvin2 c f = 
    let fToCelius t = (t - 32) * 5 / 9
        cToKelvin t = t + 273.16
        fToKelvin t = cToKelvin (fToCelius t)
    in if c > fToCelius f then cToKelvin c else fToKelvin f

And using where approach

hotterInKelvin3 :: Double -> Double -> Double
hotterInKelvin3 c f = 
    if c > fToCelius f then cToKelvin c else fToKelvin f
    where
        fToCelius t = (t - 32) * 5 / 9
        cToKelvin t = t + 273.16
        fToKelvin t = cToKelvin (fToCelius t)

I prefer the where one because I can stop reading quicker but they said I would take time to workout and sometimes preference

More on Functions

High Order functions

Now I know more about signature and how to read them

applyTwice :: (a->a) -> a -> a
applyTwice f x = f (f x)

Make must more sense, as the signature shows

  • the first argument is a function take a type a and returning a type a,
  • the second a value of type a and
  • the result is of type a

Built-in Map function

let increment x = x + 1
map increment [1,2,3] -- [2,3,4]

Built-in Filter function

And filter, loving the mod operation

let isEven n = n `mod` 2 == 0
filter isEven [2,3,4] -- [2,4]

Built-in Foldl function

Finally something new, foldl, i.e. apply operation start from position using array from left to right (lispy)

foldl (+) 0 [1,2,3,4,5] -- 15

Sort function and others

And now first use of libraries

import Data.List (sort)
sort [10, 2, 5, 3, 6, 7, 4, 8, 9, 1]

Other functions were zipWith, takeWhile, dropWhile

-- All
all even [1,2,4] -- False
any even [1,2,4] -- True

Lambda Expressions

This is a shortcut to writing unnamed function. We do this by

  • specifying a back slash
  • specifying arguments
  • an arrow
  • specifying body
print((\c -> c * 9/5 + 32) 25) -- 77.0

Precedence and Associativity

In Haskell the operators have a precedence, like looking at type with :t you can see this with :i (+). The higher the number the higher the precedence. If two have the same then then the associativity decides. So for (+) the :i shows infixl (i.e. left)

Curried Functions

All functions take on argument in Haskell. Hopefully this explains it as all functions are broken down and applied

Function application $ Operator

Gosh tired and messed with my brain but I think it means a $ will usually put parentheses around the arguments following

(2 *) 3 + 4 -- ((2 *) 3) + 4     = 10
(2 *) $ 3 + 4 -- (2 *) (3 + 4)   = 14

max 5 4 + 2   -- ((max 5) 4) + 2   = 7
max 5 $ 4 + 2 -- (max 5) (4 + 2)  = 6

The biggest use of $ is to replace a set of parentheses to make code more readable

show ((2**) (max 3 (2 + 2)))  -- 16
show $ (2**) (max 3 (2 + 2))  -- 16
show $ (2**) $ max 3 (2 + 2)  -- 16
show $ (2**) $ max 3 $ 2 + 2  -- 16

Function Composition

Here we can combine functions in one call. This just looks like chaining to me but here is the example like in Maths when you have f{g(x)} First lets look a the signature of the dot operator with my new found signature skills. The result of the second function the same type as the input to the first function and the result is the same type as the output of the first function

(.) :: (b->c) -> (a -> b) -> a -> c
f . g = \x -> f  (g x)

And an example

-- IsEven function
isEven :: Int -> Bool
-- Is Odd function
isNotEven :: Bool -> String

isEven x = if x `rem` 2 == 0 
then True
else False

isNotEven x = if x == True
  then "This is an Even Number"
else "This is an Odd Number"

main = do
    print((isNotEven.isEven) 5)

Point Free Style

Did not like the sound of this. Surely easier to decide and go with it. But here we are for reference

Recursion

Sum Example

Lets start with sum, I did not know at the beginning but (x:xs) takes a list and splits it into first element + rest

sum' :: [Int] -> Int
sum' [] = 0 -- No elements no value
sum' (x:xs) = x + sum xs

This works out as

sum'[1,2,3,4,5]  = 1 + sum' [2,3,4,5]
                 = 1 + 2 + sum'[3,4,5]
                 = 1 + 2 + 3 + sum'[4,5]
                 = 1 + 2 + 3 + 4 + sum'[5]
                 = 1 + 2 + 3 + 4 + 5 + sum'[]
                 = 1 + 2 + 3 + 4 + 5 + 0
                 = 15

Where would we be without Factorial

Stone a crow

The one thing which wasn't immediately clear we that the function we do a pattern matching. I.E. if you comment of the sum' to only match on 0 then you will get the error

Non-exhaustive patterns in function sum'

Note a single quote on a function is pronounced prime. e.g. sum' = sum prime

Product Example

This points out the fact you need to think about the base case. Does not work for 0

product :: [Int] -> Int
product [] = 1 -- No elements then 1
product (x:xs) = x * sum xs

This works out as

sum[1,2,3,4,5]  = 1 * product [2,3,4,5]
                = 1 * 2 * product[3,4,5]
                = 1 * 2 * 3 * product[4,5]
                = 1 * 2 * 3 * 4 * product[5]
                = 1 * 2 * 3 * 4 * 5 * product[]
                = 1 * 2 * 3 * 4 * 5 * 1
                = 120

Factorial Example

Where would we be without Factorial

-- Factorial
factorial :: Int -> Int
-- Base case
factorial 0 = 1
-- Recursive case
factorial n = n * factorial (n - 1)
factorial 5 = 5 * factorial[4]
            = 5 * 4 * factorial[3]
            = 5 * 4 * 3 * factorial[2]
            = 5 * 4 * 3 * 2 * factorial[1]
            = 5 * 4 * 3 * 2 * 1 * factorial[]
            = 5 * 4 * 3 * 2 * 1 * 1
            = 120

Steps to Create Recursion

Here is recommended steps

  • Write down Type: This will help define the function
  • Enumerate the possible cases based on inputs
  • Between the above cases identify simplest and define
  • Think about what you have available
  • Define the rest
  • Reflect

Drop Example

For this we drop n elements from a list. This highlight that we should consider the following cases

  • We are using numbers so 0 or other
  • We are using a list so empty or non-empty

A reminder of more parameters more arrows and that the last is the return value

drop' :: Int -> [a] -> [a]
-- Cases to consider
drop' : 0 [] = -- 0 and Empty list
drop' : 0 (x:xs) = 0 and Non-Empty list 
drop' : n [] = non-zero and Empty list
drop' : n (x:xs) = non-zero 0 and Empty list

So we can answer 3 of the 4 cases

drop' :: Int -> [a] -> [a]
drop' : 0 [] = [] -- 0 and Empty list
drop' : 0 (x:xs) = x:xs -- what we sent is what we get back
drop' : n [] = []
drop' : n (x:xs) = drop' (n-1) xs

Improving on this and knowing we are really just pattern match we get

drop` :: Int -> [a] -> [a]
drop` : _ [] = [] -- 0 and non-zero with Empty List can be matched with _
drop` : 0 xs = xs -- confused me as xs is the rest but there are no brackets so xs is all
--Not Required drop' : n [] = []
drop` : n (_:xs) = drop' (n-1) xs -- x not used

Finally what if the pass a negative number of elements, lets assume none

drop' :: Int -> [a] -> [a]
drop' _ [] = []
drop' n xs | n <= 0 = xs -- check for negative
drop' n (_:xs) = drop' (n-1) xs
-- So now
(-3) [1,2,3] -- [1,2,3]

foldr and the Primitive Recursive Pattern

Well looking at sum and product examples above we see that there is a pattern and it is the same as the built in function foldr. This pattern is known as the ```Primitive Recursive Pattern

sum` :: [Int] -> Int
sum` foldr (+) 0 

product` :: [Int] -> Int
product` foldr (*) 1

Non Parameterized Types

Type Synonyms

We can use Type Synonyms to make a type have a more readable but the same functionality. e.g

type Address = String
type Value = Int
type Id = String
-- Makes the function name more readable
generateTxId  :: Address -> Address -> Value -> Id
generateTxId from to value = from ++ to ++ show value

New Types

We can make a new type

data Colour = Red | Green | Blue

Value Parameters

We have value parameters associated with our types

data Shape = Circle Float | Rectangle Float Float

Record Syntax

Record Syntax allows us to define our type constructors much like record in C#.

data Employee = Employee { name :: String, experienceInYears :: Float } deriving show

matt = Employee "Matt" 5

-- Update to another Matt
newMatt = matt { experienceInYears = 6.0 }

Parameterized and Recursive Types

Type Synonyms

We can also use parameterized Synonyms where we identify commonality

...
type Company = (CompanyId, Address)
type Provider = (ProviderId, Address)
-- Instead we could have
type Entity  a =  (a, Address)

--Examples
company = (123, ("Fred St", 999)) :: Entity CompanyId
provider = ("String", ("Fred St", 999)) :: Entity ProviderId

Parameterized Type

So we can create types with an unspecified type. We constrain it with the fat arrow (=>) for the type we are using. I guess this is like generics

--data Box a = Empty | Has a

box = Has(1:: Int)

addN:: Num  a => a-> Box a -> Box a
addN _ Empty = Empty
addN n (Has a) = Has (a+n)

addN 3 Box -- Has 4

So an example

data Tweet = Tweet 
  { content :: String,
    likes :: Int,
    comments :: [Tweet]
  }

-- Example 
tweet:: Tweet
tweet = Tweet "I am angry about something" 5
    [ Tweet "Me too" 0 [],
      Tweet "It make me angry you are angry" 2
         [ Tweet "I have no ideas" 3 [] ] 
    ]
-- Get total likes for above
engagement :: Tweet -> Int
engagement Tweet { likes = l, comments = c} = l + length c + sum(map engagement c)

engagement tweet --- 13

Binary Tree

Searching a binary tree seems pretty simple.

The important thing I learned was you can constrain with the fat arrow what a can be. The function has an a and a Tree of type a.

data Tree a = EmptyT | Node a (Tree a) (Tree a)

elemTree :: (Ord a) => a -> Tree a -> Bool
elemTree v EmptyT = False
elemTree v (Node x left right)
    | v == x = True
    | v > x = elemTree v right
    | v < x = elemTree v left

Some Notes

  • The type of a value constructor gives you the quantity and type of its values it takes
  • The kind' of a type constructor gives you the quantity and kind of types it takes

Reading of kinds

  • '*' means concrete type
  • '* -> *' means type constructor takes a single concrete type and returns another concrete type
  • '* -> (* -> *) -> *' means type constructor that takes one concrete type, and one single parameter type constructor and returns a concrete type

newType Keyword

Think they wanted to make it hard. This seems pretty pointless but I will wait and see. Types create with newType need to have exactly one constructor with exactly one parameter/field

newType Color a  = Color a
newType Product a = Product { getProduct a}

And the answer was, it improves performance in the compiler - Sigh!!!

TypeClasses

Introduction

This is an overview of what typeclasses are which I got from [here] and made sense to me which is the main struggle for documentation for me.

What is a typeclass

First of all a typeclass is uses the keyword class. Here is the Num typeclass

class Num a where
  (+) :: a -> a -> a
  (-) :: a -> a -> a
  (*) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a

It provides several a set of functions which you either need to implement or derive

Deriving a typeclass implementation

You can do this by using the deriving keyword when defining you data type

 
data Pokemon = Pokemon
  { pokedexId   :: Int
  , name        :: String
  , pokemonType :: [String]
  , abilities   :: [String]
  } deriving (Eq)

This is only possible because the compiler can infer Eq on the data types used in the Pokemon data type

Defining a typeclass implementation with instance

To define our own typeclass implementation we can use the instance keyword. You must not now have deriving

data Pokemon = Pokemon
  { pokedexId   :: Int
  , name        :: String
  , pokemonType :: [String]
  , abilities   :: [String]
  }

instance Eq Pokemon where
  pokemon1 == pokemon2 = pokedexId pokemon1 == pokedexId pokemon2

Knowing What to implement

We can find what we need to implement by using the info command

*Main> :info Eq
...
{-# MINIMAL (==) | (/=) #-}

Examples of Type Classes

Here are some examples of common typeclasses to force me to keep the idea in my head.

  • Num
  • Integral
  • Fractional
  • Show -- Debugging
  • Read -- Reverse of Show convert string to type e.g. read :: [Int] "[1,2,2]" we get [1,2,3]

Creating a Type Class

To create a type class you need to

  • Create a type class stating some behaviors
  • Make a Type instance of that Type Class with implementation of those behaviors

Overloading

  • In Haskell Overloading is multiple implementations of function or value with the same name

Example 1 Defining Type Class

The Eq is already defined but here is how it could work. It looks like we are just defining overloaded operators like C++

-- Define class 
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

-- Here is our data type
data PaymentMethod = Card | Cash | CC

-- Create instance of our PaymentMethod
Instance Eq PaymentMethod where
-- ==
Card == Card = True
Cash == Cash = True
CC == CC = True
_== _ = False

-- /=
Card /= Card = False
Cash /= Cash = False
CC /= CC = False
_== _ = True

Now this was easy but can be done better because we have repeated ourselves with the Not Equal bit. We use some called Mutual Recursion, with is when we define the negative in terms of the positive and vice-versa. Probably wont remember but the name will allow me to google it but here is the improved version

class Eq a where
  (==), (/=) :: a -> a -> Bool
  x /= y = not (x ==y) 
  x == y = not (x /=y)

-- Now we only need to define the positive as the compiler will infer the negative
Instance Eq PaymentMethod where
Card == Card = True
Cash == Cash = True
CC == CC = True
_== _ = False

-- Info for Eq class
:i Eq
type Eq :: * -> Constraint
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
  {-# MINIMAL (==) | (/=) #-}
...

Because both function have the same type we can specify them in oneline. The not function which takes a boolean and returns the opposite. We can see running the info command for the Eq class we see the MINIMAL implementation is == or /=

Define Eq and Ord for Parameterized Types

Here hope we can define instances for parameterized types (Box in this case). We simply handle the non parameterized part and pass the Eq and Ord functions down to the concrete type.

-- Implement Eq for Box a
instance (Eq a) => Eq (Box a) where
  Empty == Empty = True
  Empty == _ = False
  _ == Empty = False
  (Has x) == (Has y) = x == y

-- Implement Ord for Box a
instance (Ord a) => Ord (Box a) where
  compare Empty Empty = EQ
  compare Empty _ = LT
  compare _ Empty = GT
  compare (Has x) (Has y) = compare x y

Example 2 Type Class

So here is another example. I guess I am hoping these examples will help as I have compiled them and they work. You seem to create a class much like other languages or operate on the types you create

data Box a = Empty | Has a deriving Show
data Present t a = EmptyPresent t | PresentFor t a deriving Show

class Container c where
    isEmpty :: c a -> Bool
    contains :: Eq a => c a -> a -> Bool
    replace :: c a -> b -> c b

instance Container Box where
  isEmpty Empty  = True
  isEmpty _      = False
  contains (Has x) y = x == y
  contains Empty _ = False
  replace _ = Has

instance Container (Present t) where
  isEmpty (EmptyPresent _)  = True
  isEmpty _ = False

  contains (PresentFor _ x) y = x == y
  contains(EmptyPresent _) _ = False
  
  replace (PresentFor tag _) x = PresentFor tag x
  replace (EmptyPresent tag) x = EmptyPresent tag

main = do
    print $ Has False `contains` False
    print $ isEmpty (Has 'a')
    print $ PresentFor "Tommy" 5 `contains` 5
    print $ PresentFor "Tommy" 5 `replace` "Arduino"

Deriving

We can use the deriving keyword to implement some on the type classes e.g.

data Choice = No | Idk | Yes deriving (Eq, Ord,. Show, Bounded, Enum) 
-- So ordering of variables matters and it will using this not the label alphabetically
data PaymentMethod =  Cash |Card|CC deriving (Eq,Ord)

But be careful

data Length = M Double | Km Double deriving (Eq)
-- This won't work if you want to mix and match you meters and kilometers so we need to implement
instance Eq Length where
  (M x) == (M y) = x == y
  (Km x) == (Km y) = x == y
  (M x) /= (Km y) = x == 1000 * y
  (Km x) /= (M y) = 1000 * x == y

Basic IO

IO Actions

  • IO action can interact with and change the world outside of our program
  • A Side effect is any observable effect other than the effect of returning a value

Examples of these are
API

Screen

Keyboard

The have two IO actions
>> - then operator - IO a -> IO b -> IO b We can see it takes two parameters, with IO it performs the actions and discards the result. Though you can see a and b, it is the action produces this, not a returned value, only c is returned. Bit like cout

abc :: IO ()
abc = putStrLn "a" >> putStrLn "b" >> putStrLn "c"

abc -- prints abc

>>= - bind operator - IO a -> (a -> IO b) -> IO b With this operator the values is returned and the getLine IO action result is fed into the yellit function

yellit :: String -> IO ()
yellit str = pustStrn (str ++ "!!!!!")

yellitBack :: String -> IO ()
yellitBack = getline >>= yellit


-- Yellit ouputs
yellit "Hey" -- Outputs Hey!!!!!

-- YellitBack ouputs
YellitBack ouputs
-- After typing How's it going
-- How's it going!!!!!

Chatty Bot

We can use the do notation and the left arrow binding. This looks a lot better

finalChattyBot :: IO ()
finalChattyBot =
  putStrLn "Hey! What's your name?"
    >> getLine
    >>= ( \name ->
            putStrLn ("Nice to meet you, " ++ name ++ "!")
              >> putStrLn (lettersInName name)
              >> putStrLn ("So, " ++ name ++ ", what do you do for fun?")
              >> getLine
              >>= ( \hobby ->
                      putStrLn ("Are you kidding, " ++ name ++ "! I love " ++ hobby ++ "!")
                  )
        )
    >> putStrLn "OK, bye!"


finalChattyBotDo :: IO ()
finalChattyBotDo = do
  putStrLn "Hey! What's your name?"
  name <- getLine
  putStrLn ("Nice to meet you, " ++ name ++ "!")
  putStrLn (lettersInName name)
  putStrLn ("So, " ++ name ++ ", what do you do for fun?")
  hobby <- getLine
  putStrLn ("Are you kidding, " ++ name ++ "! I love " ++ hobby ++ "!")
  putStrLn "OK, bye!"

File IO

Reading

Very easy

main = do
    contents <- readFile "movies.txt"
    putStr contents

Writing

main = do
    let movies = ["The Godfather", "The Shawshank Redemption", "The Dark Knight", "Pulp Fiction", "The Lord of the Rings: The Return of the King"]
    writeFile "movies.txt" (unlines movies)
    putStrLn "Movies written to movies.txt"

Modules

Importing

We can import using the import command. Going to [https://hackage.haskell.org] provides help on packages and the search is great

Example Importing

import System.Directory
import Data.List

find' :: String -> IO [FilePath]
find' str = do
  entry <- listDirectory "."
  -- the isInfixOf compares string in entry
  let found = filter (str `isInfixOf`) entry
  return found

main :: IO ()
main = do 
    putStrLn "Provide Search "
    str <- getLine
    found <- find' str
    putStrLn ("These Entries Match" ++ show found)

Importing one or loads or conflicting

To import a part you specify the part you want in brackets e.g.

import Data.List (isInfixOf, sort)

To import all less some things

import Data.List hiding(isInfixOf, sort)

To import via namespace you add qualified to force it to be used

import qualified Data.Map

To import via namespace and rename namespace

import qualified Data.Map as Map

Making a modules

Modules are directory based. I.E. You make a directory and name the module to that name. You do not need a module in each subdirectory so a module for User/Actions/Battle can be

module User.Actions.Battle where

data Golem = Golem { gAttack :: Int, gHp :: Int } deriving Show
data Player = Player { pAttack :: Int, pHp :: Int } deriving (Show)
data Battle = Fight | RunAway deriving (Show, Read)

battle :: IO Bool
battle = undefined

An example of this can be found [here]

Prelude

Gosh rust, this is module loaded by default when creating Haskell

Cabal

This is the packaging system, by default it uses hackage.

  • cabal update
  • cabal init (start new package)
  • cabel build
  • cabel exec ForestGame
  • cabel run (build and run)
  • cabel repl (run the REPL)

Very like npm. Here is an example. The build-depends must be ok for your install version

cabal-version:      3.0
name:               ForestGame
version:            0.1.0.0
license:            MIT
license-file:       LICENSE
author:             Robertino Martinez
maintainer:         robertino.martinez@iohk.io
category:           Game
build-type:         Simple
extra-doc-files:    CHANGELOG.md


executable ForestModule
    import:            warnings
    main-is:           Main.hs
    other-modules:     Forest.Level1
                     , User.Actions.Move
    build-depends:     base ^>=4.16.4.0
                     , random
    hs-source-dirs:    app
    default-language:  Haskell2010

They usually have an app with Main.hs and src with all of the library functions.

Language Extension

You can configure Haskell to use language extensions. The one shown was the NumericUnderscores e.g. 1_000 instead of 1000. To do this you add a #pragma to you code

{-# LANGUAGE NumericUnderscores #-}

A list of these can be found [here]
It did show the extension TypeApplications but I did not understand it. It showed a function declaring the type returned e.g.

 startingStamine <- randomIO (10_000, 20_000) :: IO Int

We can put

 startingStamine <- randomIO @Int (10_000, 20_000)

I think it is because the return value is polymorphic so you need to specify. This just makes it more readable

Exception Handling

Example 1

Here we can define our exception and make it and instance of Exception. On from that we can define a handler for it

import Control.Exception

data TrafficLightException = TrafficLightIsOff | WrongColor String
    deriving (Show)

instance Exception TrafficLightException

nextMove :: String -> IO String
nextMove color = case color of
  "Red"    -> return "Stop!"
  "Yellow" -> return "Wait!"
  "Green"  -> return "Go!"
  "Black"  -> throwIO TrafficLightIsOff
  _        -> throwIO . WrongColor $ color ++ " is not a valid color!"

main :: IO ()
main = do
  putStrLn "What color is the traffic light?"
  color    <- getLine
  response <- nextMove color `catch` handler
  putStrLn $ "I'll " ++ response
  main
  
  where
    handler :: TrafficLightException -> IO String
    handler e = do
      -- do whatever you want...
      putStrLn $ "WARNING: " ++ show e
      case e of
        TrafficLightIsOff -> return "Proceed with caution."
        WrongColor _      -> return "Stop the car and shut down!"

Maybe type

They stressed using of Maybe to hand errors. It mentioned readMaybe, aside from tools for developers, rarely have I written something which uses user input.

Example 2

Here is an example of catching a division by zero in Haskell

import Control.Exception

main = do
  result <- try (evaluate (div 5 0)) :: IO (Either SomeException Int)
  case result of
    Left ex -> putStrLn $ "Caught exception: " ++ show ex
    Right val -> putStrLn $ "The result was: " ++ show val

Tips

  • using cabal repl you can import you library into the repl
  • hackage show details of package
  • hoogle search for package using function e.g. String -> IO, or If you know the function but not the package
  • undefined Force type check to run, or stop the program at a given point like throw
  • type holes use underscore to get information about expected value.
  • --- >>> foo ["A","B"] Putting this is the source allow inline evaluation

Functors

Introduction

The definition I got from C++ is function object is a function with state.

Simple C++ Example

struct Value {
   int m_result {0};
   
   int operator()(int newResult) {
       m_result = newResult;
       return m_result;
   }
}

int main() {
  Value v;
  v(42);
  std::cout << v.m_result << std::endl;
}

Better C++ Example

They went on to provide a second C++ example where the Comparator was used as a good use of a functor. Calling the function Comparator with the state of the goblins.

struct Goblin {
    int health;
    int strength;
    Goblin(int health, int strength) : health(health), strength(strength) {};

    bool operator<(const Goblin& other) const {
        return health < other.health;
    }
};

struct GoblinComparator {
    bool operator()(const Goblin& a, const Goblin& b) const {
        return a.strength < b.strength;
    }
};

int main() {
    std::vector<Goblin> goblins {
        {5, 25},
        {3, 25},
        {100, 1}
    };

    std::sort(goblins.begin(), goblins.end(), GoblinComparator());

    for(const auto& goblin : goblins) {
        std::cout << "Health: " << goblin.health << " Strength: " << goblin.strength << std::endl;
    }

    return 0;
}

// Health: 100 Strength: 1
// Health: 5 Strength: 25
// Health: 3 Strength: 25

Much Better Example

Googling to make sure I understood the idea of a functor, found a much better example. which was a logger and all makes sense now.

class Logger {
public:
    Logger(const std::string& fileName) {
        m_output.exceptions(m_output.badbit | m_output.failbit);
        try
        {
            m_output.open(fileName, std::ofstream::out);
            m_output << "Logger started" << std::endl;
        }
        catch (const std::ofstream::failure& e)
        {
            std::cerr << "Failure: " << e.what() << std::endl;
            std::string error = "Could not open file: " + fileName;
            std::cout << error << std::endl;
            throw std::runtime_error(error);
        }

    }

    void operator() (const std::string& message) {
        m_output << message << std::endl;
    }

    public:
    ~Logger() {
        m_output.flush();
        m_output.close();
    }

private:
    std::ofstream m_output;
};

int main() {
    Logger logger("log.txt");
    logger("Hello, world!");
    return 0;
}

Back to Haskell

Arggghhhhh

To I am still struggling but this is what the definition of a functor is and therefore some kind of type definition, i.e. anything which looks like this is a functor

class Functor f where
    fmap :: (a->b) -> f a -> f b

The fmap definition is I guess

(a->b) -> f a -> f b

This was explained as
fmap takes a function which is of type a and results in a type b
It applies f of a and gives you a f of b

Map and FMap

So some progress, the tutor showed a couple of things which start to make more sense in the interactive session. They the types which I knew but here to keep context

:t False -- Bool
:t "hello" -- String
:t 'T' -- Char
:t 12  -- Num a=>a (an instance of typeclass Num not type)
-- Now lets look at our Maybe (like Option but instead on Some and None, Just and Nothing)
:t Nothing -- Maybe a
:t Just 5 -- Num a => Maybe a

It is the last one where thing are starting to make sense, the output suggests you put a in and you get a out. So we can now use the kind or k to look a the type constructors

:k Bool -- :: *
:k String -- :: *
:k Char -- :: *
:k Int -- :: *
-- Now lets look at our Maybe
:k Maybe -- * -> * Which means it is a function converting from type to type

So looking a map

-- Example
map (+2) [1,2,3] -- [3,4,5]

-- Looking at type
:t map -- map :: (a -> b) -> [a] -> [b]

This looks a lot like the type of fmap. fmap came first but because lists are so useful they made a specific implementation of fmap called map for lists.
You can use fmap for any shape of data but you have to tell it how to traverse the data you are using. In the example you talked about trees.

Third Video

So opening line was great, a functor is a TYPECLASS. A typeclass, you can think of as an interface you need to implement.

class Functor f where
    fmap :: (a->b) -> f a -> f b

As Oliver Hardy might say, now we are getting somewhere. The video went on to distinguish between types where are wrapped and unwrapped. They eluded to the Maybe type which can be a Just or Nothing. They demonstrated unwrapped values can be evaluated

 
(+3) 9 -- 12 Where 9 is unwrapped
-- but
(+3) (Just 9) -- Will not work

Now the Functor for some types are already defined including, Maybe, Either and List so to demonstrate we can make our on type, a copy of Maybe using this

 
data Maybe2 a = Just2 a | Nothing2 deriving Show

Now we can make our own Functor for Maybe2

 
instance Functor Maybe2 where 
    fmap func (Just2 a) = Just2 (func a)
    fmap func Nothing2 = Nothing2

Putting it together we have

 
import Data.Functor

data Maybe2 a = Just2 a | Nothing2 deriving Show

instance Functor Maybe2 where
  fmap f (Just2 x) = Just2 (f x)
  fmap f Nothing2 = Nothing2

main = do
    print $ fmap (+1) (Just2 10) -- Just2 11
    print $ fmap (+2) (Nothing2) -- Nothing2

Trees

Now we come on to the tree, the most common example I have seen. For fmap and a tree we want to apply a function at the tip of the branch to get everything. This will involved recursion as a branch can have many branches

So here is a C# Recursive approach before we start

 
    // Function to perform inorder traversal
    static void inorderTraversal(Node node) {
      
        // Base case
        if (node == null)
            return;

        // Recur on the left subtree
        inorderTraversal(node.left);
      
        // Visit the current node
        Console.Write(node.data + " ");
      
        // Recur on the right subtree
        inorderTraversal(node.right);
    }

But with Haskell this does seem somewhat more readable

 
import Data.Functor

data Tree a = Tip a | Branch (Tree a) (Tree a) deriving Show

instance Functor Tree where
  fmap f (Tip x) = Tip (f x)
  fmap f (Branch left right) = Branch (fmap f left) (fmap f right)

x = Branch (Tip 4) (Branch (Tip 5) (Tip 6))

main = do
    print $ fmap (*2) x

Finally

Whilst I understood functor for both C++ and Haskell, I did not understand how a C++ functor related to a Haskell one too much. Time means I march on to Monads

Applicative

Introduction

When they say applicative, they are referring to the <*> symbol. Now we use applicatives they always have a defined Functor, this has the following TypeClass definition

class Functor => Applicative f where
    pure :: a-> f a
    (<*> :: f (a ->b) -> f a -> f b

You will notice that the second clause is exactly the same as Functor aside from the f

Example 1

Here is an example of using the applicative

import Data.Functor

data Maybe2 a = Just2 a | Nothing2 deriving Show

instance Functor Maybe2 where
  fmap f (Just2 x) = Just2 (f x)
  fmap f Nothing2 = Nothing2

instance Applicative Maybe2 where
  pure = Just2
  Just2 f <*> Just2 x = Just2 (f x)
  -- _ <*> _ = Nothing2

main = do
    print $ fmap (+1) (Just2 10)
    print $ fmap (+2) (Nothing2)
    print $ Just2 (+3) <*> Just2 10 -- Just2 13

Example 2

Its time for the tree again

 
import Data.Functor

data Tree a = Tip a | Branch (Tree a) (Tree a) deriving Show

instance Functor Tree where
  fmap f (Tip x) = Tip (f x)
  fmap f (Branch left right) = Branch (fmap f left) (fmap f right)

instance Applicative Tree where
  pure x = Tip x
  Tip f <*> x = fmap f x
  Branch left right <*> x = Branch (left <*> x) (right <*> x)

x = Branch (Tip 4) (Branch (Tip 5) (Tip 6))

main = do
    print $ Tip (*4) <*> x -- Branch (Tip 16) (Branch (Tip 20) (Tip 24))
    print $ Branch (Tip (+1)) (Tip (*2)) <*> x -- Branch (Branch (Tip 5) (Branch (Tip 6) (Tip 7))) (Branch (Tip 8) (Branch (Tip 10) (Tip 12)))

Monads

So this is the Monad bit which I what I started on to learn this. Like Functors, the explanation may have been written in greek. I have no idea what he meant

class Monad m where
    return :: a -> m a
    -- Bind
    (>>=) :: m a -> (a -> m b) -> m b
    -- Then
    (>>) :: m a -> m b -> m b 
    x >> y = x >>= \_ -> y
    -- Fail
    fail :: String => m a
    fail msg = error msg

He preceded to speak about 3 laws of Monads

--- Three Laws of Monads:

--- 1 Left Identity:
return a >> = f
--- the same as : f a

-- 2 Right identity:
x >>= return
-- the same as: x  {Value not changed}


-- 3 Associativity:
(m >>= f) >>= g
m >>= (\x -> f x >>=g)

Moniods

The explanation was again useable. This is left here to fill in later