Tutorial: Haskell

Haskell is a functional programming language. Functions and programs are like mathematical equations. It is really fun to play with.

Contents:

The Basics

  • Basics

  • Algebraic Data Types

  • Maybe Data Type

Abstract Data Types:

  • Linked List

  • Queue

  • Arithmetic Tree

  • Graphs

  • Heaps

The Basics

To Install:

For Linux, it can be installed with apt

sudo apt-get install haskell-platform

GHCI:

GHCI is an interactive environment for the Glasglow Haskell Compiler. It is used to interactively evaluate expressions, and compile and run haskell files.

//In terminal:
~$ ghci

//To quit:
:quit

//To get type of variable:
:t [variable]

//To load from file:
:l [filename]

Comments:

-- I am a single line comment
{--I am a multiline comment --}

Types:

-- Ints
a=5

-- Floats
a=5.23

-- Chars
a='a'

-- Strings:
a="abcd"

-- or 
a=['a', 'b', 'c', 'd']

-- Lists, must all have the same data type
-- Ex: An integer list, type: [Int] 
a=[1,2,3,4]

-- Tuples, can have different types
-- Ex: 
a=(1,2,'a', [1,2,3])

List Operations:

Examples of list operations performed on list a=[1,2,3,4]

-- A list of ints 
a=[1,2,3,4]

-- Get the first element: 
head a

1

-- Get every element but the first: 
tail a

[2,3,4]

-- Get the last element: 
last a

4

-- Get every element but the last: 
init a

[1,2,3]

-- Returns true if list is empty, false otherwise 
null a

False

-- reverse the order of the list 
reverse a

[4,3,2,1]

-- get length of list 
length a

4

-- get first n elements of the list
take 2 a

[1,2]

-- drop first n elements of the list
drop 2 a

[3,4]

-- get largest element in a list
maximum a

4

-- get smallest element in a list
minimum a

1

-- get the sum of the elements in the list
sum a

10

-- get the product of the elements in the list
product a

24

-- check if element is a list
elem 5 a

False

-- concat 2 lists: 
a ++ [5,6]

[1,2,3,4,5,6]

-- Add an element to a list: 
5 : a

[5,1,2,3,4]

-- OR: 
[5] ++ a

[5,1,2,3,4]

List Comprehensions:

Is the process of generating a list using a mathematical expression, always in the format: [operation | range, condition]

-- Example: double every element in a list
a = [1,2,3,4]

[x*2 |x <- a]

[2,4,6,8]

-- double only even numbers:
[x*2 | x <- a, (even x)]

[4,8]

-- double even numbers, triple odd numbers:
[if even x then x*2 else x*3 | x <- a]

[3,4,9,8]

Sequence/Range Operator:

Gets all the values within a certain range, can be used with chars as well. Must be ascending order

-- Ex:
[1..10]

--OUT:
[1,2,3,4,5,6,7,8,9,10]

['a'..'g']
--OUT:
"abcdefg"

[10..1]
--OUT:
[]

Functions:

Functions consist of 2 parts: declaration, and definition. A function declaration has the format: myFunc :: arg1 -> arg2 -> outVal, and a function definition defines the operation to be performed by the function.

-- Ex: Sum of 2 ints
mySum:: Int -> Int -> Int
mySum x y = x+y

--To call the function:
(mySum 2 4)
-- OUT:
6

Pattern Matching:

Pattern matching allows functions to execute differently depending on their arguments

-- Ex: Recursive function calculating factorial
myFact:: Int -> Int
myFact 0 = 1
myFact n = n * (fact (n-1))

--To call the function:
myFact 4
-- OUT:
24

Guards:

Match 1 or more expressions, use guards to test some property of an expression, guards are seperated by |, they allow for different operations to be performed depending on the input params.

-- Ex: Recursive function calculating factorial

myFact :: Int -> Int
myFact n | n == 0 = 1
        | otherwise = (fact (n-1))

--To call the function:
myFact 4
-- OUT:
24

Where clause:

Used to break complex expressions into smaller parts

-- Ex: Recursive function calculating nth term of the fibonacci sequence
myFib:: Int -> Int
myFib 0 = 0
myFib 1 = 1
myFib n = m where m = (myFib (n-1)) + (myFib (n-2))

--To call the function:
myFib 5
-- OUT:
5

Lambda Expressions:

anonymous function, can only be used once (ie. cannot be called), is denoted by \

-- Ex: Increase by one
((\x-> x + 1) 4)
--OUT:
5

Higher Order Functions:

Accumulation (Folding) - apply a binary operator between the elements of a list,
Format: fold (operator) (accumulator) list,
foldl - accumulator left side arguement (ex. accumulator - myList[i]),
foldr - accumulator right side arguement (ex. myList[i] - accumulator)

-- Ex:
foldl (+) 0 [1,2,3]

6

foldl (-) 12 [1,2,3]

6

foldr (-) 12 [1,2,3]

-10

Transformation (Mapping) - call a function on every element in a list
Format: map (operator) list

map (+2) [1,2,3]

[3,4,5]

map (*2) [1,2,3]

[2,4,6]

Selection(Filtering) - Test a predicate for every element of a list
Format: filter (boolean condition) myList

filter (even) [1,2,3]
[2]

filter (<10) [8, 9, 10]

[8,9]

filter (=='a') ['a','a', 'b', 'c']

"aa"

Algebraic Data Types:

Algebraic Data Types are user defined data types, sort of like objects. They can have attributes, and can be used in operations.

-- Ex:

data Food = Cake | Spinach | Apple

myFoods :: Food -> [Char]
myFoods Cake = "Yum!"
myFoods Spinach = "Yuck"
myFoods Apple = "Nice Healthy Choice"
  
-- Run
myFoods Apple
--OUT:
"Nice Healthy choice"   

Example: Truth Table:

-- A) Define enumerated data type

data LogicDataType =  Trwue
   | Fawlse
   | Unkwown
   deriving Show

-- Logical NOT
ternaryNOT :: LogicDataType -> LogicDataType
ternaryNOT Trwue = Fawlse
ternaryNOT Fawlse = Trwue
ternaryNOT a = a

--  Logical AND
ternaryAND :: LogicDataType -> LogicDataType -> LogicDataType
ternaryAND Trwue Trwue = Trwue
ternaryAND Fawlse a = Fawlse
ternaryAND a Fawlse = Fawlse
ternaryAND a b = Unkwown

-- Logical OR 
ternaryOR :: LogicDataType -> LogicDataType -> LogicDataType
ternaryOR Fawlse Fawlse = Fawlse
ternaryOR Trwue a = Trwue
ternaryOR b Trwue = Trwue
ternaryOR a b= Unkwown

-- Run
ternaryOR Trwue Fawlse
-- OUT:
Trwue

Maybe Data Type:

An enumerated type that allows you to work with a value that might not exist
Prototype: data Maybe a = Just a | Nothing deriving (Eq, Ord)

-- Ex: Using Maybe for division
import Data.Maybe
  
safeDivide :: Int -> Int -> Maybe Int
safeDivide n 0  =  Nothing
safeDivide n m  =  Just (n `div` m)
  
-- Run
safeDivide 12 3
--Out:
Just 4
  
safeDivide 12 0
--Out:
Nothing

Abstract Data Types

Here are some abstract data types/structures that I implemented for fun

Linked List:

A Linked List is relatively easy to implement. The linked list is made up of nodes. Each node has a value and a LList, which is just the next node in the list, and the final node has LList Null

data LList = Null | Node Int LList

instance Show LList where
        show Null = "| . |"
        show (Node a llist) = "| "++(show a)++" |---->"++show(llist)


addToList :: LList -> Int -> LList
addToList Null a = (Node a (Null))
addToList (Node a llist) b = (Node a (addToList llist b))

sumOfList :: LList -> Int
sumOfList Null = 0
sumOfList (Node a llist) = a+(sumOfList llist)

arrayToLList :: [Int] -> LList
arrayToLList [] = Null
arrayToLList (x:xs) = (Node x (arrayToLList xs))

removeAtK :: LList -> Int -> LList
removeAtK Null a = Null
removeAtK (Node a llist) 0=llist
removeAtK (Node a llist) b=(Node a (removeAtK llist (b-1)))

addAtK :: LList -> Int -> Int -> LList
addAtK Null i a = (Node a (Null))
addAtK (Node a llist) 0 b= (Node b (Node a (llist)))
addAtK (Node a llist) i b= (Node a (addAtK llist (i-1) b))
Example: Running the Linked List program:
-- Run it:
-- Create a Linked List: 
a = (Node 2 (Node 4 (Node 5 (Node 1 (Null)))))

| 2 |---->| 4 |---->| 5 |---->| 1 |---->| . |

-- Add to List: 
(addAtK a 3 9)

| 2 |---->| 4 |---->| 5 |---->| 9 |---->| 1 |---->| . |

-- Remove from List: 
(addAtK a 2)

| 2 |---->| 4 |---->| 1 |---->| . |

-- Convert an Array to a Linked List: 
(arrayToLList [1,2,3,4])

| 1 |---->| 2 |---->| 3 |---->| 4 |---->| . |


-- Get the sum of the Linked List: 
(sumOfList a)

12

Queue

For a queue we define the back and front of the queue, which allows us to push and pop.

data MyQueue = Back Int MyQueue | Front Int | Node Int MyQueue 

instance Show MyQueue where
show (Front curr) = " "++(show curr)++" "
show (Back curr next) = "| "++(show curr)++" | "++(show next) 
show (Node curr next) = (show curr)++" |"++(show next)

insertElem :: MyQueue -> Int -> MyQueue
insertElem (Back curr next) n = (Back n (Node curr (next)))

deleteElem :: MyQueue -> MyQueue
deleteElem (Back curr next) = (Back curr (deleteElem next)) 
deleteElem (Node curr (Front n)) = (Front curr)
deleteElem (Node curr next) = (Node curr (deleteElem next))
Example: Running the Queue program:
-- Run it:
--Create a queue
a = (Back 2 (Node 3 (Node 1 (Front 8))))

| 2 | 3 |1 | 8 

--Insert (Push) an element
insertElem a 5

| 5 | 2 |3 |1 | 8 

--Delete (Pop) an element
deleteElem a

| 2 | 3 | 1

Arithmetic Tree:

Define a binary search tree where each leaf has a float, and each internal node has an operator

-- For School Assignment
-- Type Literal x
x :: Char
x = 'x'
  
-- Operators  
data Op= Add | Sub | Mul | Div 
  
--Show the Operators  
instance Show Op where
show Add= " + "
show Sub= " - "
show Mul= " * "
show Div= " / "
  
-- Helper Function to apply the operators  
applyOper :: Op -> Float -> Float -> Float
applyOper Add ls rs = ls + rs
applyOper Sub ls rs = ls - rs
applyOper Mul ls rs = ls * rs
applyOper Div ls rs = ls / rs
  
-- Recursive Data Structure for the Arithmetic Tree  
data Expr = Lit Float | Var Char | Oper Op Expr Expr
  
---------------------------------
  
instance Show Expr where 
show (Lit a) = " "++(show a)++" "
show (Var a) = " "++[a]++" "
show (Oper op ls rs) = "("++(show ls)++(show op)++(show rs)++")"
  
-- Solve the expression given the value of the literal x
solveExpr :: Expr -> Float -> Maybe Float
solveExpr (Var a) n = Just n
solveExpr (Lit a) n = Just a
solveExpr (Oper Div ls rs) n = (safeDiv a b) where 
                             Just a = (solveExpr ls n)
                             Just b = (solveExpr rs n)
solveExpr (Oper op ls rs) n = Just (applyOper op a b) where 
                            Just a = (solveExpr ls n) 
                            Just b = (solveExpr rs n)
  
-- Perform a division such that if divided by 0 it will not return an error  
safeDiv :: Float -> Float -> Maybe Float
safeDiv n 0 = Nothing
safeDiv n m = Just (n/m)
  
--------------------------
-- Draw Tree
-- Purpose: Function to draw tree
  
drawTree :: Expr -> [[Char]]
drawTree (Lit a)=["  "++(show a)++" "] 
drawTree (Var a)=["  "++[a]++" "]
drawTree tree = (drawLevels tree (getLevels tree))
  
-- Purpose: takes the tree and max number of levels
-- recursively draws the left hand side of the tree and then the right side
-- IN: 
--  - Expr - an Expression Tree
--  - Int - the number of layers in the tree
-- OUT:
--   - [[Char]] - the string representation of the subtree
  
drawLevels :: Expr -> Int -> [[Char]]
drawLevels (Lit a) n = [" ---- "++(show a)]
drawLevels (Var a) n= [" ---- "++[a]++" "] 
drawLevels (Oper op ls rs) n= ["(" ++ (show op) ++ ")"++(drawLeft ls)] ++ (drawRight ls n)  ++  (drawLevels rs (n-1))
  
-- Purpose: draws the right side of the tree 
-- draws the vertical lines, as well as the right leaves as they must be on the same line in order for the tree to be connected
-- IN: 
--  - Expr - an Expression Tree
--  - Int - the number of layers (vertical lines to draw)
-- OUT:
--   - [[Char]] - the string representation of the subtree
drawRight :: Expr -> Int -> [[Char]]
drawRight (Lit a) n =[" |"]
drawRight (Var a) n =[" |"]
drawRight (Oper op ls (Var a)) n=[(dupl (n-1) " |         "),(dupl(n-2) " |         ")++" ---- "++[a]]++(drawRight ls (n-1))
drawRight (Oper op ls (Lit a)) n=[(dupl (n-1) " |         "),(dupl (n-2) " |         ")++" ---- "++(show a)]++(drawRight ls (n-1))
drawRight (Oper op ls rs) n =[" |"]++(drawRight ls n)++(drawRight rs n)
  
-- Purpose: draws the "left hand side" of the tree
drawLeft :: Expr -> [Char]
drawLeft (Lit a)="  ---- "++(show a) 
drawLeft (Var a)="  ---- "++[a]
drawLeft (Oper op ls rs)=" ---- ( " ++ (show op) ++ " )"++(drawLeft ls)
  
-- Purpose: duplicates a string a given amount of times
  
dupl :: Int -> [Char] -> [Char]
dupl 0 a=""
dupl n a=a++(dupl (n-1) a)
  
-- Purpose: gets the max number of layers in the tree or subtree, used for proper spacing when drawing the tree
getLevels:: Expr -> Int
getLevels (Lit a)=1
getLevels (Var a)=1
getLevels (Oper op ls rs)=if (getLevels ls) >= (getLevels rs) then 1+(getLevels ls) else 1 + (getLevels rs)

Example: Running the Arithmetic Tree Program:

-- Run it:
  
-- Create Tree
a = (Oper Add (Oper Sub (Lit 5) (Var x)) (Lit 2))

(( 5.0  -  x ) +  2.0 )
  
-- Solve the Tree given x
solveExpr a 3
  
Just 4.0
  
-- Draw Tree
putStr (unlines (drawTree a))
  
( + ) ---- (  -  )  ---- 5.0
  |          |         
  |          ---- x
  |
  ---- 2.0

Graphs:

This was difficult as generally data types must be recursive. All the other ones implemented are acyclic, so they are easy, but graphs have cycles, so they are harder. An adjancency list-like and adajcency-matrix-like structures were used.

data AdjacencyList = Null | Node Int AdjacencyList

  instance Show AdjacencyList where
  show Null = "|/|"
  show (Node a adjlist) = "|" ++ (show a) ++ "|-->" ++ (show adjlist)

data GraphList = NodeList Int AdjacencyList GraphList | None

instance Show GraphList where
  show None = "\n"
  show (NodeList x al gr) = (show x) ++ "|  |->"++ (show al) ++ "\n" ++ (show gr) 

adjacentListToList :: AdjacencyList -> [Int]
adjacentListToList Null = []
adjacentListToList (Node x adjlist) = x : (adjacentListToList adjlist)

getAdjacentVertices :: GraphList -> Int -> [Int]
getAdjacentVertices None v = []
getAdjacentVertices (NodeList x al gr) v = if x == v then (adjacentListToList al) else (getAdjacentVertices gr v) 

data GraphMatrix = GNode Int [Int] GraphMatrix | MNull

instance Show GraphMatrix where
show MNull = "\n"
show (GNode v l gr) = (show v) ++ " | " ++ (show l) ++ "\n" ++ (show gr)


main = do
let g = (NodeList 1 (Node 2 (Node 5 Null)) (NodeList 2 (Node 1 (Node 5 (Node 3 (Node 4 Null)))) (NodeList 3 (Node 2 (Node 4 Null)) (NodeList 4 (Node 2 (Node 5 (Node 3 Null))) (NodeList 5 (Node 4 (Node 1 (Node 2 Null))) None)))))
putStrLn $ "Graph g: \n" ++ (show g)

putStrLn $ "Adjacent to 2:\n" ++ (show (getAdjacentVertices g 2))

let gr = (GNode 1 [0, 1, 0, 0, 1] (GNode 2 [1, 0, 1, 1, 1] (GNode 3 [0, 1, 0, 1, 0] (GNode 4 [0, 1, 1, 0, 1] (GNode 5 [1, 1, 0, 1, 0] MNull)))))
putStrLn $ "Graph g: \n" ++ (show gr)

Using the following as an example:

Result:

Heaps:

Heaps can be represented as lists, or as trees. This program converts between them several different ways. Note: it is missing several fundamental operations.

data BinTree = Node Int BinTree BinTree | Null

  instance Show BinTree where
  show Null = ""
  show tree = (unlines (drawLevels tree (getLevels tree)))

drawLevels :: BinTree -> Int -> [[Char]]
drawLevels Null n= ["/"]
drawLevels (Node x Null Null) n  = [" ---- "++(show x)]
drawLevels (Node x ls rs) n =  ["(" ++ (show x) ++ ")"++(drawLeft ls)] ++ (drawRight ls n)  ++  (drawLevels rs (n-1))

drawRight :: BinTree -> Int -> [[Char]]
drawRight (Node x Null Null) n =[" |"]
drawRight Null n= [" |"]
drawRight (Node r ls (Node x Null Null)) n=[(dupl (n-1) " |         "),(dupl (n-2) " |         ")++" ---- "++(show x)]++(drawRight ls (n-1))
drawRight (Node r ls rs) n =[" |"]++(drawRight ls n)++(drawRight rs n)

-- Purpose: draws the "left hand side" of the tree
drawLeft :: BinTree -> [Char]
drawLeft Null = "/"
drawLeft (Node x Null Null) ="  ---- "++(show x) 
drawLeft (Node x ls rs)=" ---- ( " ++ (show x) ++ " )"++(drawLeft ls)

-- Purpose: duplicates a string a given amount of times
dupl :: Int -> [Char] -> [Char]
dupl 0 a=""
dupl n a=a++(dupl (n-1) a)


treeToList :: BinTree -> [Int]
treeToList Null = []
treeToList (Node r ls rs) = r : (treeToList ls) ++ (treeToList rs)

levelOrderToList' :: BinTree -> Int -> [Int]
levelOrderToList' Null n = []
levelOrderToList' (Node r ls rs) 0 = [r]
levelOrderToList' (Node r ls rs) n =  (levelOrderToList' ls (n-1)) ++ (levelOrderToList' rs (n-1))

getLevels :: BinTree -> Int
getLevels Null = 0
getLevels (Node r ls rs) = if (ln > rn) then ln+1 else rn+1 where
                  ln=(getLevels ls)
                  rn=(getLevels rs)
                  

walkLevels :: BinTree -> Int -> Int -> [Int]
walkLevels t m 0 = []
walkLevels t n m = (levelOrderToList' t n) ++ (walkLevels t (n+1) (m-1))

levelOrderToList :: BinTree -> [Int]
levelOrderToList t = (walkLevels t 0 (getLevels t))  

listToTree :: [Int] -> BinTree
listToTree [] = Null
listToTree (x:[]) = Node x Null Null
listToTree (x:xs) = (Node x (listToTree (take n xs)) (listToTree (drop n xs))) where n = (if mod (length xs) 2 == 1 then (div ((length xs)+1) 2) else (div (length xs) 2)) 


rebuildTree :: BinTree -> BinTree
rebuildTree Null = Null
rebuildTree (Node r Null Null) = (Node r Null Null)
rebuildTree (Node r (Node ls lls lrs) Null) | r > ls = (Node r (rebuildTree (Node ls lls lrs)) Null)
                                         | otherwise = (Node ls (Node r lls lrs) Null)
rebuildTree (Node r Null (Node rs rls rrs)) | r > rs = (Node r (rebuildTree (Node rs rls rrs)) Null)
                                         | otherwise = (Node rs (Node r rls rrs) Null) 
rebuildTree (Node r (Node ls lls lrs) (Node rs rls rrs)) | r > ls && r > rs = (Node r (rebuildTree (Node ls lrs lls)) (rebuildTree (Node rs rls rrs))) 
                                                      | r < ls && ls > rs = (Node ls (Node r lls lrs) (Node rs rls rrs))
                                                      | otherwise =  (Node rs (Node ls lls lrs) (Node r rls rrs))

buildHeap' :: BinTree -> Int
buildHeap' Null = 0
buildHeap' (Node r Null Null) = 0
buildHeap' (Node r Null (Node rs rls rrs)) | r > rs = (buildHeap' (Node rs rls rrs))
                                        | otherwise = 1
buildHeap' (Node r (Node ls lls lrs) Null) | r > ls = (buildHeap' (Node ls lls lrs))
                                        | otherwise = 1
buildHeap' (Node r (Node ls lls lrs) (Node rs rls rrs)) | r > ls && r > rs = (buildHeap' (Node ls lls lrs)) + (buildHeap' (Node rs rls rrs))
                                                     | otherwise = 1

buildHeap :: BinTree -> BinTree
buildHeap tr = if (buildHeap' tr) == 0 then tr else (buildHeap (rebuildTree tr))

main = do
let t = (Node 1 (Node 2 (Node 4 Null Null) Null) (Node 3 Null Null))
putStr $ "Tree t: \n"++(show t)

putStrLn $ "Tree to List: \n"++(show (treeToList t))
putStrLn $ "Tree to Level Order List: \n"++(show (levelOrderToList t))

let arr = [4,3,7,1,8,5]
let tr = (listToTree arr)
putStrLn $ "List to Tree: \n" ++ (show tr)
let heap = (buildHeap tr)
putStrLn $ "List to Heap: \n" ++ (show heap)
putStrLn $ "Heap to Level Order List: \n"++(show (levelOrderToList heap))

Example Usage:

Create a tree and convert to list. Convert a list to tree, and convert list to max heap