19cse313 - Principles of Programming Languages: Recursion in Haskell
19cse313 - Principles of Programming Languages: Recursion in Haskell
LANGUAGES
Recursion in Haskell
RECURSION
• Recursion is actually a way of defining functions in which the function is
applied inside its own definition
• Recursion is important to Haskell because unlike imperative languages,
computations are done in Haskell by declaring what something is instead
of declaring how to get it.
• There are no while loops or for loops in Haskell and instead we many
times have to use recursion to declare what something is.
EXAMPLE –MAXIMUM FUNCTION USING RECURSION
--maxrec.hs
maxrec :: (Ord a) => [a] -> a
maxrec [] = error "maximum of empty list"
maxrec [x] = x ghci> maxrec [5,2,3,1,4]
5
maxrec (x:xs) ghci> maxrec [454,678,989,12,1]
989
| x > maxTail = x ghci> maxrec []
| otherwise = maxTail *** Exception: maximum of empty list
CallStack (from HasCallStack):
where maxTail = maxrec xs error, called at maxrec.hs:3:13 in main:Main
ghci> maxrec [1]
1
Check if the head is greater
than the maximum of the rest of
the list. If it is, return the
head.Otherwise, return the
maximum of the rest of the list.
SAMPLE EVALUATION OF MAXREC
• Let's take an example list of numbers and check out how this would work on
them: [2,5,1].
• If we call maximum' on that, the first two patterns won't match.
• The third one will and the list is split into 2 and [5,1].
• The where clause wants to know the maximum of [5,1], so we follow that route.
• It matches the third pattern again and [5,1] is split into 5 and [1].
• Again, the where clause wants to know the maximum of [1] .
• Because that's the edge condition, it returns 1.
• Finally! So going up one step, comparing 5 to the maximum of [1] (which is 1),
we obviously get back 5.
• So now we know that the maximum of [5,1] is 5.
• We go up one step again where we had 2 and [5,1] .
• Comparing 2 with the maximum of [5,1], which is 5, we choose 5.
IMPERATIVE VERSION OF MAXIMUM ELEMENT IN A LIST
#include <stdio.h>
int main()
{
int size, i, largest;
printf("\n Enter the size of the array: ");
scanf("%d", &size);
int array[size];
printf("\n Enter %d elements of the array: \n", size);
for (i = 0; i < size; i++)
{
scanf("%d", &array[i]);
}
largest = array[0];
Algorithm !
for (i = 1; i < size; i++)
• Set up a variable to hold the maximum value so far
{ • Loop through the elements of a list
if (largest < array[i]) • If an element is bigger than then the current maximum value,
largest = array[i]; • Replace it with that element.
} • The maximum value that remains at the end is the result
A CLEANER VERSION OF MAXREC USING MAX
--maxrec1.hs
maxrec1 :: (Ord a) => [a] -> a
maxrec1 [] = error "maximum of empty list"
maxrec1 [x] = x
maxrec1 (x:xs) = max x (maxrec1 xs)
myreverse [] = []
myreverse (x:xs) = myreverse xs ++ [x]
elem' a (x:xs)
| a == x = True Empty list contains no
elements
| otherwise = a `elem'` xs
Courtesy: https://beginnersbook.com/2015/02/quicksort-program-in-c/
THINKING RECURSIVELY – SUMMING UP !
• Pattern Observed:
• Define an edge case and then
• define a function that does something between some element and the function
applied to the rest.
• Example: A sum is the first element of a list plus the sum of the rest of the list.
• A product of a list is the first element of the list times the product of the rest of the
list.
• The length of a list is one plus the length of the tail of the list etc.
• Edge cases: Some scenario where a recursive application doesn't make sense.
• E.g., For a list - empty list; For a tree – Leaf node
• Factorial - some number and the function applied to that number modified.
Factorial of 0 doesn’t make sense and since multiplication, set as 1 (identity for
product)
• For lists, sum of empty lists is 0 (Identity for addition)
• For quick sort – Edge case and identity are empty lists
THINKING RECURSIVELY – SOME GUIDELINES…
• So when trying to think of a recursive way to solve a problem…
• Try to think of when a recursive solution doesn't apply and see if you can
use that as an edge case,
• think about identities and
• think about whether you'll break apart the parameters of the function (for
instance, lists are usually broken into a head and a tail via pattern
matching) and on which part you'll use the recursive call.
Reference:
Miran Lipovaca - Learn You a Haskell for Great Good!_ A Beginner's Guide-No Starch Press (2011)
THANK YOU