Blog Hub | Previous | Next |
Post 2
Defining functions in Haskell
The .hs File extension
Haskell source code files have the extension .hs. These files can be loaded in GHCi and compiled into programs with GHC. Let’s explore a little of what you can do with haskell files.
Function Definitions and Recursion
Writing functions in Haskell is very similar to writing pure mathematical definitions. The interpreter is able to parse your definitions and allocate memory automatically, so you don’t need to worry about memory management or anything like that. Functions often take the form of recursive definitions with a a base case or a few, and a recursive case.
Take, for example the factorial function. As you may know, n! = 1 _ 2 _ … _ n-1 _ n for any integer n. Also, 0! evaluates to 1. Using those two facts, let’s write a function fact
that takes a single integer as an input and returns an integer as an output.
fact 0 = 1
fact x = x * fact (x - 1)
Save your file and enter GHCi from within the same folder as your new haskell file. To load your new expresion into GHCi, you use the :load [file name]
command. With your program loaded, try a few expressions in GHCi:
fact 0
fact 4
fact 12334
fact -1
The Haskell interpreter automatically assigns types to the parameters in the expression. Since Haskell “knows” that passing a negative integer would cause the function to run forever, it completely disallows it from happening.
Some text editors (I would recommend visual studio code) will show you what the implicit typing of your function is and will give you the option to declare it on the spot.
Haskell functions rely on pattern matching to ensure the correct case is chosen for any given value passed to the function. The order in which you give the patterns is very important because the interpreter will attempt to match patterns with the given parameters sequentially.
Some more examples and some more syntax
Here is a function that calculates the length of a list recursively
len [] = 0
len (x : xs) = 1 + len xs
Here are some functions that split a list into it’s odd and even number indicies:
select_evens :: [a] -> [a]
select_evens [] = []
select_evens [x] = []
select_evens (x : y : ys) = y : select_evens (ys)
select_odds [] = []
select_odds [x] = [x]
select_odds (x : y : ys) = x : select_odds (ys)
When using the : operator, as long as the right side evaluates to a list, you’re good to go! Because of that, Haskell will evaluate the right side first. That’s how (x : y : xs) works!
If you need to have some control flow, try the “if then else” structure! When used in a recursive function, the function only recurses if the condition you give it is met So this function checks if the current element of a list is equal to the key, m. Otherwise it recurses on the next element of the list
member m [] = False
member m (x : xs) =
if m == x
then True
else member m xs
You can also use haskell’s lazy boolean operators: The boolean operator | means “or”, however the right side of the operation isn’t evaluated if the left side is true Likewise, when evaluating &&, if the left side is false then the right side isn’t evaluated |
member2 [] y = False
member2 (x : xs) y = (x == y) || (member2 xs y)
Here’s how to reverse the order of a list:
revert [] = []
revert (x : xs) = append (revert (xs)) (x : [])
You can nest conditional statements too!
less_equal ([], []) = True
less_equal (x : xs, y : ys) =
if len xs /= len ys
then False
else
if x <= y
then less_equal (xs, ys)
else False
As part of the larger merge_sort algorithm, the merge algorithm takes two sorted lists and combines them into another sorted list. It does this by checking the first element of each list and adding the smaller of the two to a new list. It then moves on to the next element of the list that contained the smaller value, and recurses.
merge ([], []) = []
merge (x : xs, []) = x : xs
merge ([], y : ys) = y : ys
merge (x : xs, y : ys) =
if (x <= y)
then x : merge (xs, y : ys)
else y : merge (x : xs, ys)
The actual merge_sort funcion simply splits an unsorted list in half, then recurses. Once all the lists have a length of 1 or 0, the lists are merged in pairs until all the smaller lists are merged into one sorted list.
merge_sort [] = []
merge_sort [x] = [x]
merge_sort xs = merge (merge_sort ys, merge_sort zs)
where
n = len (xs) `div` 2
(ys, zs) = splitAt n xs
The “where” operator lets you define values to be used in an expression. In this case, n is the index at which larger lists are split into smaller ones, ys is the first half of the larger list, and zs is the second half.
Blog Hub | Previous | Next |