简体   繁体   中英

Having Trouble Understanding Haskell Merging Function

I have an assignment to make a Haskell function to merge 2 lists together without using the ++ operation. I found the following code online and it works as intended but I need help understanding how and why it works. If someone can take me through a step by step on how this function works, I would greatly appreciate it. I am VERY new to Haskell so assume you are explaining this to a 5 year old lol.

merge :: [a] -> [a] -> [a]
merge []     ys = ys
merge (x:xs) ys = x : (merge xs ys)

A list in Haskell is defined as a linked list, with [] as the empty list, and (:) is the " cons " where the first parameter is the head (the first element), and the second element the tail (so a list with the remaining elements). This thus means that a list like [1,4] is represented as (1 : (4 : [])) , or more can canonical (:) 1 ((:) 4 []) .

If we now want to merge two lists [1,4] and [2,5] for example we thus call merge with merge (1 : (4 : [])) (2 : (5 : [])) . The first rule of merge checks if the first parameter is an empty list, which is not the case, so we continue the second rule. This checks if the first list uses a cons data constructor, which is the case. So it unifies x with the head of the list (here 1 ), and xs with the rest of the list (here [4] ). This thus means that it replaces this with:

merge (1 : (4 : [])) (2 : (5 : []))  ->  1 : merge (4 : []) (2 : (5 : []))

Now in the recursive call, the first clause will be checked again, but (:) 4 [] again does not match with the empty list [] , so we check the second clause, and again this matches. So in this call x unifies with 4 , and xs with [] :

merge (4 : [])) (2 : (5 : []))  ->  4 : merge [] (2 : (5 : []))

the last recursion has the empty list data constructor as first parameter. This thus matches with the pattern in the first clause. We thus return the second list:

merge [] (2 : (5 : []))  ->  (2 : (5 : []))

the result is thus a list:

(1 : (4 : (2 : (5 : []))))

or in short:

[1,4,2,5]

A list is either empty, or non-empty. If it's non-empty, then it has a head (the first element) and a tail (the rest of the list, minus that first element). If we can say what merge does when its first argument meets both of those cases, then we will have handled every possible case - because that first list, being a list, is either empty or not.

So suppose first we are merging the empty list with another list. In this case, clearly the result is just that other list - there's nothing to merge. This is the easy case, covered in the first line of the function: merge [] ys = ys .

So we're left with the case where the first list is non empty. As previously stated, that means it has a head and a tail. It should be easy to see that, in merging this list with another, the result is the same as if we were to merge the tail with the other list, then add the head to the front afterwards. That's what the second line of the function says, in the language of pattern matching (in particular using the "cons" constructor (:) for breaking a list into a head and tail): merge (x:xs) ys = x : (merge xs ys) .

Once we have those, we can compute merge for any two lists. Not only do these two cases cover all possible ones, but crucially they allow us to "work backwards" to always get an answer - because in the recursive case, we express merge (x:xs) in terms of merge xs , and xs is shorter than (x:xs) , so by repeating this process we always eventually get back to the empty list case*

A simple example should demonstrate: merge [1, 2, 3] [4, 5, 6] successively becomes:

1 : (merge [2, 3] [4, 5, 6])
1 : (2 : (merge [3] [4, 5, 6]))
1 : (2 : (3 : (merge [] [4, 5, 6])))
1 : (2 : (3 : [4, 5, 6]))

whic we would more normally write as [1, 2, 3, 4, 5, 6] .

*actually, this argument relies on that first list being finite. If it's infinite, the recursion never stops - but this still isn't a problem, as the process still allows us to compute any finite number of elements as we want. In fact the result is simply that first list, for any finite number of elements. But don't worry about this case too much if it doesn't make sense to you yet, infinite data structures don't turn up every day!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM