Pure Functional Programming with Haskell
Concepts of Programming Languages
Sebastian Macke
Rosenheim Technical University
Sebastian Macke
Rosenheim Technical University
Examples: exp, sin, cos, max, min.
Advantages
The most prominent characteristics of functional programming are as follows
Functional programming does not support state, so there are no side-effect results and we can write error-free codes.
Functional programs consist of independent units that can run concurrently. As a result, such programs can be more efficient.
Functional programming supports lazy evaluation like Lazy Lists, Lazy Maps, etc.
Functional programming supports distributed computing
6www.jdoodle.com/execute-haskell-online
www.onlinegdb.com/online_haskell_compiler
Online tutorials
hackage.haskell.org/package/CheatSheet-1.11/src/CheatSheet.pdf
9main = print "Hello, World!"
1. Define a function with name "main" with no input and output parameters.
Execute the function print, which takes one arbitrary argument. In this case a string
Online editor:
102 + 15 => 17 40 * 100 => 4900 5 == 5 => True 5 == 5.0 => True True && False => False not False => True "Hello" ++ " World" => "Hello World" [1,2,3] ++ [4,5,6] => [1,2,3,4,5,6] [1,2,3] !! 1 => 2 "hey" == ['h','e','y'] => True -- Strings are just lists of characters "hey" !! 0 => 'h'
r = 5 -- globally defined variable main = do let n = r -- local defined "variable" print n let m = n + 1 print m
The do keyword is used to define a block of code. This sequence of instructions nearly
matches that in any imperative language
Actually a variable is just a function with no arguments
12Defines a function inc that takes one argument x and returns x + 1
inc x = x + 1 main = print (inc 1)
inc x = x + 1 main = print $ inc 1
The $ operator is used to avoid parentheses. It has the lowest precedence of all operators
13
Function, which takes multiple arguments, can be partially applied
Here, the function add takes two arguments a and b and returns a + b
add a b = a + b main = print $ add 1 2
add a b = a + b main = do let c = add 1 -- partial application print $ c 2
add a b = a + b main = print( (add 1) 2)
TODO
15add :: Int -> Int -> Int add a b = a + b main = print $ add 1 2
add :: Int -> (Int -> Int)
square x = x * x tripleSquare = square . square . square main = print $ tripleSquare 2 -- returns 256
max a b = if a > b then a else b main = print $ Main.max 1 2 -- max already exists, so be more specific
max a b | a > b = a | otherwise = b main = print $ Main.max 1 2 -- max already exists, so be more specific
factorial n = if n < 2 then 1 else n * factorial (n - 1) main = print $ factorial 10
factorial 0 = 1 factorial n = n * factorial (n - 1) main = print (factorial 10)
Haskell uses two fundamental structures for managing several values: lists and tuples.
They both work by grouping multiple values into a single combined value.
addsub a b = (a + b, a - b) main = print $ addsub 1 2
add a b = (a + b, a - b) main = do let (x, y) = add 1 2 print x; print y print $ fst (add 1 2) -- first element print $ snd (add 1 2) -- second element
main = do print [1,2,3] print (1:2:3:[]) -- same as [1,2,3] print [1 .. 3] -- same as [1,2,3] print ([1,2,3] ++ [4,5,6]) print $ [1,2,3] !! 1 -- access element at index 1 print $ head [1,2,3] -- first element print $ tail [1,2,3] -- all elements except the first one print $ last [1,2,3] -- last element print $ init [1,2,3] -- all elements except the last one
myhead (x:xs) = x mytail (x:xs) = xs main = do print $ myhead [1,2,3] print $ mytail [1,2,3]
(x:xs) is a pattern that matches a list with at least one element
reverse [] = [] reverse (x:xs) = Main.reverse xs ++ [x] main = print $ Main.reverse [1,2,3]
isPalindrome [] = True isPalindrome [x] = True isPalindrome (x:xs) = x == (last xs) && isPalindrome (init xs) main = print $ isPalindrome [1,2,3,2,1]
import Data.List revsort = (reverse . sort) main = print $ revsort [2,8,7,10,1,9,5,3,4,6]
1. Sort takes a list and returns a sorted list. The result of sort is pipelined to reverse
2. prints the result of revsort for the given table
osc :: [Integer] osc = 0 : 1 : osc main = print (osc!!10)
1. Define a function osc, which returns a list of integers of unknown size
2. Define a function osc, which returns oscillating numbers 0,1,0,1,0,1. There is no break condition in this list!
3. Execute the function fibs which actually should return an infinite list. But is not evaluated yet.
Take the 10th element out of the list. Print the result
This example explains the concept of lazy evaluation.
The list osc is not evaluated until it is needed.
The function osc is called only once, but the list osc is evaluated many times.
The list osc is evaluated only as much as needed.
map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs main = print $ map (+1) [1,2,3]
add x y = x + y map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : Main.map f xs main = print $ Main.map (add 1) [1,2,3]
With the filter function
main = print $ filter (<3) [4,3,2,1,2,3,4] -- returns [2,1,2]
we can finally define the famous quicksort algorithm
quickSort [] = [] quickSort (x:xs) = quickSort (filter (<x) xs) ++ [x] ++ quickSort (filter (>=x) xs) main = print $ quickSort [2,8,7,10,1,9,5,3,4,6]
Also Haskell have to perform IO
import Data.Time.Clock import Control.Concurrent main :: IO () -- return type is IO (). The function is executed regardless of the return value main = do x <- getCurrentTime; print x; threadDelay 1000000; x <- getCurrentTime; print x
The <- sign is used to assign the result of a non-pure function to a variable
29github.com/s-macke/concepts-of-programming-languages/blob/master/docs/exercises/Exercise6.md
30