Home > Enterprise >  How Base conditions in haskell works?
How Base conditions in haskell works?

Time:09-17

I'm starting to do my firsts steps in Haskell. I have worked with recursion problems in the past using Python. Just for practice purposes, I'm trying to write a function witch reverse a list:

reverse2 lst =  if length lst == 1
                then lst !! 0
                else (last lst) : (reverse2 (init lst))

I get the error:

SimpleFunctions.hs:9:22: error:
    • Occurs check: cannot construct the infinite type: a ~ [a]In the expression: (last lst) : (reverse2 (init lst))
      In the expression:
        if length lst == 1 then
            lst !! 0
        else
            (last lst) : (reverse2 (init lst))
      In an equation for ‘reverse2’:
          reverse2 lst
            = if length lst == 1 then
                  lst !! 0
              else
                  (last lst) : (reverse2 (init lst))
    • Relevant bindings include
        lst :: [a] (bound at SimpleFunctions.hs:7:10)
        reverse2 :: [a] -> [a] (bound at SimpleFunctions.hs:7:1)
  |
9 |                 else (last lst) : (reverse2 (init lst))
  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.

But if I change by

reverse2 lst =  if null lst
                then []
                else (last lst) : (reverse2 (init lst))

It works!

Why the first option is not correct?

CodePudding user response:

You work with lst !! 0 but this returns an element of the list, not a list itself. You thus should wrap the first item in the list, for example with:

reverse2 :: [a] -> [a]
reverse2 lst =  if length lst == 1
                then [lst !! 0]
                else (last lst) : (reverse2 (init lst))

The time complexity of this is however O(n2) since determining the length of the list, and obtaining the last item takes linear time, and we have to do this for all items.

Usually this problem is solved by making use of an accumulator: an extra parameter that stores the reversed list thus far and makes recursive calls with a slightly altered accumulator until we reach the end of the list.

In that case we thus implement reverse as:

reverse :: [a] -> [a]
reverse = go []
    where go rv [] = rv
          go rv (x:xs) = go (x:rv) xs

here rv is the thus far obtained reverse of the list. For each item in the list, we prepend the reverse list with that item. If the list is thus [1,4,2,5], then we will make recursive calls that look like:

reverse [1,4,2,5]
  → go [] [1,4,2,5]
  → go [1] [4,2,5]
  → go [4,1] [2,5]
  → go [2,4,1] [5]
  → go [5,2,4,1] []
  → [1,4,2,5]

CodePudding user response:

Let's write out a type for your function. A reverse function takes a list and returns a list. So something like

reverse2 :: [a] -> [a]

Now, let's look at the implementation.

reverse2 lst =  if length lst == 1
                then lst !! 0
                else (last lst) : (reverse2 (init lst))

Both arms of the if need to return a [a], because that's the expected type. The else branch is fine; it makes a recursive call, then prepends an element. The result we get is a list. Great! The then branch, on the other hand, is problematic. lst is a [a], and (!!) has type

(!!) :: [a] -> Int -> a

So lst !! 0 has type a, not [a]. To return a singleton list, we need to be explicit about it.

reverse2 lst =  if length lst == 1
                then [lst !! 0]
                else (last lst) : (reverse2 (init lst))

Of course, now this function will fail on the empty list, since taking last and init of the empty list will fail. So we need a case for the empty list.

reverse2 :: [a] -> [a]
reverse2 [] = []
reverse2 lst =  if length lst == 1
                then [lst !! 0]
                else (last lst) : (reverse2 (init lst))

And at this point your null solution is looking simpler.

  • Related