Recommendations
When trying to understand recursion, it will be easier for you to think about how the algorithm works for a given input. It's easy to hang up on what the execution path looks like, so instead ask yourself questions such as:
- What happens if I go through an empty list?
- What happens if I pass a list with one item?
- What happens if I pass a list with many items?
Or, for recursion by numbers:
- What happens if I pass a negative number?
- What happens if I pass 0?
- What happens if I pass a number greater than 0?
The structure of a recursive algorithm is often just a matter of covering the above cases. So let's see how your algorithms behave to understand this approach:
maximum
maximum [] = error maximum [1] = 1 maximum [1, 2] = 2
As you can see, the only interesting behavior is # 3. The rest just guarantee that the algorithm completes. Considering the definition,
maximum' (x:rest) = max x (maximum' rest)
The challenge of this using [1, 2] extends to:
maximum [1, 2] ~ max 1 (maximum' [2]) ~ max 1 2
maximum' works by returning a number that in this case knows how to process recursively using max . Consider another case:
maximum [0, 1, 2] ~ max 0 (maximum' [1, 2]) ~ max 0 (max 1 2) ~ max 0 2
You can see how the maximum' recursive call on the first line is exactly the same as in the previous example.
reverse '
reverse [] = [] reverse [1] = [1] reverse [1, 2] = [2, 1]
Reverse works by taking the head of a given list and inserting it at the end. For an empty list, this does not require any work, so the basic case. Therefore, given the definition:
reverse' (x:xs) = reverse' xs ++ [x]
We will make some replacement. Given that [x] equivalent to x:[] , you can see that there are actually two values:
reverse' [1] ~ reverse' [] ++ 1 ~ [] ++ 1 ~ [1]
Simple enough. And for a two-element list:
reverse' [0, 1] ~ reverse' [1] ++ 0 ~ [] ++ [1] ++ 0 ~ [1, 0]
take '
This function introduces recursion on an integer argument, as well as on lists, so there are two basic cases.
What happens if we take 0 or less items? We do not need to take any items, so just return an empty list.
take' n _ | n <= 0 = [] take' -1 [1] = [] take' 0 [1] = []
What happens if we go through an empty list? There are no more items, so stop the recursion.
take' _ [] = [] take' 1 [] = [] take -1 [] = []
The softness of the algorithm really is to go down the list, pull out the input list and reduce the number of elements that need to be executed until none of the above basic cases stops the process.
take' n (x:xs) = x : take' (n-1) xs
So, in the case when the numerical base case is executed first, we stop before we reach the end of the list.
take' 1 [9, 8] ~ 9 : take (1-1) [8] ~ 9 : take 0 [8] ~ 9 : [] ~ [9]
In the case when the first case of the list is satisfied, we finished the elements before the counter reaches 0, and simply return what we can.
take' 3 [9, 8] ~ 9 : take (3-1) [8] ~ 9 : take 2 [8] ~ 9 : 8 : take 1 [] ~ 9 : 8 : [] ~ [9, 8]