3
votes

When I try to use the function below to implement a function like below the compiler return

parse error (possibly incorrect indentation or mismatched brackets)

The function:

demo 8 [1,2,3] should return [1,2,3,1,2,3,1,2]

 demo :: Int -> [a] -> [a] 
    let n = 0 
    demo arg [] = [] 
    demo arg (x:xs) = 
         if arg <= length (x:xs) then
             take arg (x:xs) 
         else
             let (x:xs) = (x:xs) ++ (x:xs)!!n
             arg = arg - 1
             n = n + 1
             demo arg (x:xs)

How can I correct that? Regards!

2
possibly incorrect indentation. The let..in block looks like it's incorrectly indented. Try to experiment with its indentation to get the code to compile. - Mark Seemann
let arg = arg-1 and let n=n+1 are recursive definition which define arg and n as infinite looping computations. You don't want that. You are trying to mutate variables in Haskell, which was designed so to make that impossible. That approach is too imperative to work in a FP language. Roughly, the only way to "change" a variable is to call a function with the new value, eg. f 0 = 0 ; f n = f (n-1) defines a recursion that "decreases" n until it reaches 0. - chi

2 Answers

6
votes

You are mixing imperative and functional paradigms: let n = 0, let (x:xs) = (x:xs) ++ (x:xs)!!n, arg = arg - 1, n = n + 1 are (in your code) imperative expressions. You expect the values of n, (x:xs) and arg to be modified, but functional programming does not work like that.

The function you are declaring is meant to be pure: that means that you can't expect values to be modified (what we call a "side effect"). The only thing you can do is to call a new function (or the same function) with new arguments that are computed "on the fly" from the original arguments.

Let's try to be concrete.

You can't do:

arg = arg - 1
demo arg (x:xs)

But you can do:

demo (arg - 1) (x:xs)

The latter is a call of demo with arg - 1 as argument. The value of arg was never modified.

The main problem in your code is the n variable. It should be 0 at first call, and should be increased each time arg < length (x:xs) to add the next element from (x:xs) to the end of (x:xs) itself. Thus, the list will grow in a cyclic way until we can take the desired number of elements.

To reach that goal, you have to create an auxiliary function and to "move" the recursion into that auxiliary function:

demo :: Int -> [a] -> [a]
demo arg [] = [] 
demo arg (x:xs) = demo' arg 0 (x:xs) -- n = 0
    where 
        demo' :: Int -> Int -> [a] -> [a]
        demo' arg n l = if arg <= length l 
                        then take arg l 
                        else 
                            -- call demo' with new parameters:
                            -- (x:xs) = (x:xs) ++ [(x:xs)!!n]
                            -- arg = arg - 1
                            -- n = n + 1 
                            demo' (arg-1) (n+1) ((x:xs) ++ [(x:xs)!!n]) ```

Now, from the example you have given, arg is the constant number of elements in the list you want to create, so it should not be decreased. And you don't need the deconstruction of the list in (x:xs). Finally, with a little cleanup, you have :

demo :: Int -> [a] -> [a]
demo arg [] = [] 
demo arg l = demo' arg 0 l
    where 
        demo' :: Int -> Int -> [a] -> [a]
        demo' arg n l = if arg <= length l 
                        then take arg l 
                        else demo' arg (n+1) (l ++ [l!!n])

There is a far better way to achieve this (demo n=take n.cycle), but I tried to stay close to your original implementation.

3
votes

You can't write let n = 0 at the top level. Once you remove that, the else let portion of your code is not correctly indented either, as its use (outside of a do block) is always let ... in ..., and the contents of the let portion must be equally indented. Even if it were formatted, let is recursive, so arg = arg - 1 means a value which is one less than itself, which doesn't compute.

Now, this function actually does two things: it cycles through all the elements of the list, and also limits it to a given length. Both of these are already available in the standard library.

demo n xs = take n (cycle xs)

If you wanted to write it yourself, a similar breakdown is also reasonable.

demo :: Int -> [a] -> [a]
-- Handle negative n, so we can assume it's positive below
demo n _ | n < 0 = []
-- Similarly, handle empty list, so we can assume it's non-empty below
demo _ [] = []
-- Given a positive n and non-empty list, start cycling list with limit n.
demo n list = demo' n list
  where
    -- Limit has been reached, stop.
    demo' 0 _ = []
    -- List is exhausted, cycle back to the start.
    demo' m [] = demo' m list
    -- Take the next element of the list and continue.
    demo' m (x:xs) = x : demo' (m-1) xs

Note that it was not necessary to use length, which is a good thing! length diverges on infinite lists, while this handles them gracefully.