Lecture 1

Fundamentals

FP — Functional Programming

What is function?

In math

Programming is not math!

  • Hang: loop indefinitely, take unreasonable time to compute
  • Have side-effects: read from files, send requests to web services, etc.
  • Throw exceptions: missing file, no internet connection
  • Terminate early: insufficient memory

Functions in programming can:

What is FP?

Function is the main building block of programs in FP

  • Define
  • Call
  • Compose
  • Pass as an argument
  • Inspect

In FP languages it should be easy to do the following with functions

FP Concepts

🏹 Higher-Order Functions (HOF)

🤸 Algebraic Data Types (ADT)

đŸ–ŧ Pattern Matching

💧 Purity

🧱 Immutability

🌎 Totality

đŸĻĨ Laziness

Haskell Features

Supports all FP concepts plus:

  • Static typing
  • Polymorphisms
  • Type inference
  • Layout-sensitivity
  • ML syntax
  • Automatic currying
  • Garbage collector
  • Green threads
  • Software Transactional Memory (STM)
  • ...

Haskell Toolchain

GHC (Glasgow Haskell Compiler) — the compiler

GHCi (GHC interactive) — interactive interpreter aka REPL (Read-Eval-Print-Loop)

ghcup — Haskell toolchain installer (GHC, cabal, stack, hls)

cabal — a Haskell build tool, the first one

stack — another Haskell build tool based on snapshots

HLS (Haskell Language Server) — provides LSP support for Haskell

GHCi

GHCi is a REPL (Read-Eval-Print-Loop)

Course coding conventions

ℹī¸ Terminal (or shell) commands start with $

ℹī¸ GHCi commands start with prompt Prelude> or ghci>

GHCi: Arithmetic

Prelude> 3 + 4
7
Prelude> 420 - 397
23
Prelude> 13 * (-3)
-39
Prelude> 2 + 2 * 2
6
Prelude> (2 + 2) * 2
8
Prelude> 2 ^ 16
65536
Prelude> 100 / 8
12.5
Prelude> 0.1 + 0.2
0.30000000000000004

GHCi: Comparison

Prelude> 2 + 2 == 5
False

Prelude> 0.1 + 0.2 /= 0.15 + 0.15
True
Prelude> 6 < 7
True

Prelude> 6 * 7 > 5 * 8
True

Prelude> 10 <= 5
False

Prelude> 100 >= 120
False

GHCi: Logic

Prelude> 1 < 3 && 2 <= 4
True

Prelude> 1 < 3 && 2 >= 4
False

Prelude> 1 < 3 || 2 >= 4
True

Prelude> 1 < 2 && 2 < 3 && 3 < 4
True

Prelude> True || False
True

Prelude> False || True && False
False

GHCi: Calling functions

functionName arg1 arg2 ... argN

Syntax

Examples

Prelude> not True
False

Prelude> div 7 3
2

Prelude> mod 7 3
1

Prelude> max (2 * 3) (3 ^ 2)
9

Prelude> max (min 1 10) (min (-5) 3)
1

() are important!

Prelude> div 7 3 + 2
4

Prelude> div 7 (3 + 2)
1
Prelude> div 7 + 3 2

<interactive>:40:1: error:
    â€ĸ Non type-variable argument in the constraint: Num (a -> a)
      (Use FlexibleContexts to permit this)
    â€ĸ When checking the inferred type
        it :: forall a t.
              (Integral a, Num t, Num (a -> a), Num (t -> a -> a)) =>
              a -> a

Types

Expressions have types

Prelude> :t True
True :: Bool

Prelude> :t False
False :: Bool

Prelude> :t 'F'
'F' :: Char
expr :: \mathrm{type}

ℹī¸ Use :t or :type command to view expression type in GHCi

Prelude> :t Bool
<interactive>:1:1: error: Data constructor not in scope: Bool

Types of numbers

Prelude> :t 21
21 :: Num p => p

Prelude> :t 3.5
3.5 :: Fractional p => p

⚠ī¸ Numeric constants are polymorphic in Haskell by default

Prelude> :t +d 21
21 :: Integer

Prelude> :t +d 3.5
3.5 :: Double

Types of functions

Prelude> :t not
not :: Bool -> Bool
Prelude> :t div
div :: Integral a => a -> a -> a

Prelude> :t +d div
div :: Integer -> Integer -> Integer
Prelude> :t (&&)
(&&) :: Bool -> Bool -> Bool

Prelude> :t +d (-)
(-) :: Integer -> Integer -> Integer

List

List literals

Prelude> []
[]
Prelude> [3, 1, 2]
[3,1,2]
Prelude> [3 + 4, 5 * 7, 17 - 9]
[7,35,8]
Prelude> [1, 5] ++ [3, 4, 2]
[1,5,3,4,2]
Prelude> 6 : [3, 2, 1]
[6,3,2,1]
Prelude> :t [True, False, True]
[True, False, True] :: [Bool]

Prelude> :t ['a', '1', 'F']
['a', '1', 'F'] :: [Char]

Expessions

Types

List: head/tail/last/init

Prelude> head [3, 1, 2]
3
Prelude> tail [3, 1, 2]
[1,2]
Prelude> last [3, 1, 2]
2
Prelude> init [3, 1, 2]
[3,1]

Watch your head!

Prelude> head []
*** Exception: Prelude.head: empty list

List: more functions

Prelude> reverse [4, 1, 3, 2]
[2,3,1,4]
Prelude> take 2 [4, 3, 7, 10, 9]
[4,3]
Prelude> drop 2 [4, 3, 7, 10, 9]
[7,10,9]
Prelude> null []
True
Prelude> null [3]
False
Prelude> elem 'x' ['a', 'b', 'c']
False
Prelude> concat [[3, 1], [2], [], [5, 10]]
[3,1,2,5,10]
Prelude> length [3, 1, 2]
3
Prelude> [5, 4, 6] !! 0
5

⚠ī¸ length and !! are slow operations on lists (linear time)

List: ranges

Prelude> [1 .. 10]
[1,2,3,4,5,6,7,8,9,10]

Prelude> [1, 3 .. 20]
[1,3,5,7,9,11,13,15,17,19]
Prelude> [10 .. 1]
[]
Prelude> [10, 9 .. 1]
[10,9,8,7,6,5,4,3,2,1]
Prelude> [0 .. ]
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,
24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,
66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,
87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,
106,107,108,109,110,111,112,113,114,115,116...

♾ Infinite lists

Lazy evaluation đŸĻĨ

Prelude> take 3 [0 .. ]
[0,1,2]

Prelude> take 3 (drop 5 [0 .. ])
[5,6,7]

ℹī¸ Expressions are evaluated only when needed

Prelude> [5, head [], 7]
[5,*** Exception: Prelude.head: empty list

Prelude> [5, head [], 7] !! 2
7

⚠ī¸ Be careful with infinite data structures!

Prelude> length [0 .. ]

String

Prelude> :t ['h', 'e', 'l', 'l', 'o']
['h', 'e', 'l', 'l', 'o'] :: [Char]

Prelude> :t "hello"
"hello" :: [Char]

ℹī¸ String in Haskell is a list of characters.

Prelude> "The result is: " ++ show [True, False]
"The result is: [True,False]"

Prelude> words "Hello,    Haskell    world!"
["Hello,","Haskell","world!"]

Prelude> unwords ["My", "name", "is"]
"My name is"
Prelude> take 5 "Hello world!"
"Hello"
Prelude> "Hello, " ++ "Alice!"
"Hello, Alice!"

Syntax

Define a function

functionName :: type
functionName arg1 ... argN = result

Syntax

File   : Example.hs
Module : Example
module Example where

increase :: Integer -> Integer
increase x = x + 1
*Example> increase 5
6

Loading a module

module Example where

increase :: Integer -> Integer
increase x = x + 1
$ ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/  :? for help
Prelude> increase 5

<interactive>:1:1: error:
    Variable not in scope: increase :: t0 -> t
Prelude> :l Example.hs 
[1 of 1] Compiling Example          ( Example.hs, interpreted )
Ok, one module loaded.
*Example> increase 5
6

Packages

Prelude> import Data.List

Prelude Data.List> sort [3, 1, 2]
[1,2,3]

Prelude Data.List> nub [True, False, True, True]
[True,False]

module — a collection of functions and custom data types

package — a collection of modules + metadata

Hackage — a central repository of Haskell packages

base — standard Haskell library

Prelude — the module from base imported by default

🔗 Hackage

🔗 Hackage: base

🔗 Hackage: base, Prelude

if-then-else

-- Return first element of the list or the given default value
headOrDefault :: Int -> [Int] -> Int
headOrDefault def list = if null list then def else head list
headOrDefault :: Int -> [Int] -> Int
headOrDefault def list =
    if null list
    then def
    else head list
Prelude> headOrDefault 0 []
0
Prelude> headOrDefault 0 [3, 1, 2]
3

guards

sign :: Int -> String
sign n
    | n == 0    = "Zero"
    | n < 0     = "Negative"
    | otherwise = "Positive"
Prelude> sign 0
"Zero"

Prelude> sign 5
"Positive"

Prelude> sign (-10)
"Negative"

let-in

sameThreeAround :: [Int] -> Bool
sameThreeAround list =
    let firstThree = take 3 list
        lastThree  = reverse (take 3 (reverse list))
    in firstThree == lastThree
Prelude> sameThreeAround [1 .. 5]
False

Prelude> sameThreeAround [1,2,3,4,5,1,2,3]
True
let var1 = expr1
    var2 = expr2
    ...
in result

Syntax

let var = expr in result

Example

where

appendLastTwos :: [Int] -> [Int] -> [Int]
appendLastTwos list1 list2 = lastTwo list1 ++ lastTwo list2
  where
    lastTwo :: [Int] -> [Int]
    lastTwo l = reverse (take 2 (reverse l))
Prelude> appendLastTwos [1..5] [1..10]
[4,5,9,10]

Prelude> appendLastTwos [] [3]
[3]

Immutability

Prelude> list = [3, 1, 2, 5, 10]

Prelude> list
[3,1,2,5,10]

Prelude> newList = drop 2 list

Prelude> newList
[2,5,10]

Prelude> list
[3,1,2,5,10]

📜 Values are never changed. They can be assigned only once.

How do you change if you can't?

Recursion!

No more loops!

count :: Int -> [Int] -> Int
count n list = go 0 list
  where
    go :: Int -> [Int] -> Int
    go result l =
        if null l    -- if the list is empty
        then result  -- then return our accmulated result
        else if head l == n
             then go (result + 1) (tail l)
             else go  result      (tail l)

Count number of elements equal to given number

Prelude> count 3 [1 .. 5]
1

Prelude> count 5 [1,2,3,5,3,10,5]
2

Higher-Order Functions

First-Class Functions

applyToSame :: (Int -> Int -> Int) -> Int -> Int
applyToSame f x = f x x

ℹī¸ You can pass functions as arguments to other functions!

Prelude> applyToSame div 3
1
Prelude> applyToSame mod 3
0
Prelude> applyToSame (+) 3
6
Prelude> applyToSame (*) 3
9

Îģ Lambda functions Îģ 

satisfies :: (Int -> Bool) -> Int -> String
satisfies check n
    | check n   = "The number " ++ show n ++ " passes check"
    | otherwise = "The number " ++ show n ++ " doesn't pass" 
\var1 var2 ... varN -> expression

Syntax

Example

Prelude> satisfies (\x -> x > 0) 5
"The number 5 passes check"

Prelude> satisfies (\x -> x > 0) (-3)
"The number -3 doesn't pass"

Partial application

applyTwice :: (Integer -> Integer) -> Integer -> Integer
applyTwice f x = f (f x)
Prelude> div12By = div 12

Prelude> :t +d div12By 
div12By :: Integer -> Integer

Prelude> div12By 3
4
Prelude> :t (True ||)
(True ||) :: Bool -> Bool

Prelude> :t +d (* 3)
(* 3) :: Integer -> Integer
Prelude> applyTwice (+ 20) 17
57
Prelude> applyTwice (* 3) 4
36

Standard HOFs

Prelude> map not [True, False, True, True]
[False,True,False,False]

Prelude> map (* 2) [1 .. 5] 
[2,4,6,8,10]

Prelude> filter even [3, 1, 2, 5, 4, 0, 10, 7]
[2,4,0,10]

Prelude> filter (<3) [10, 9 .. 0]
[2,1,0]

Prelude> any (> 10) [7, 5, 9, 3]
False

Prelude> concatMap (replicate 3) [1 .. 5]
[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]

Prelude> take 10 (iterate (* 2) 1)
[1,2,4,8,16,32,64,128,256,512]

Functions inside lists

Prelude> head [(* 2), div 12, (+ 10)] 5
10

Prelude> last [(* 2), div 12, (+ 10)] 5
15

ℹī¸ You can also store functions inside lists!

👩‍đŸ”Ŧ Functions are automatically curried. No need to use special syntax for calling functions, just space is enough 👾

Recap: GHCi commands

:q — quit GHCi
:t — the type of an expression

:t +d — the type of an expression with defaulting

:l — load a module

:i — information about a function or type

More sources

Lecture 1: Fundamentals

By Haskell Beginners 2022

Lecture 1: Fundamentals

Fundamentals of Functional Programming and Haskell

  • 2,137