Reputation: 975
I want to shift a list by n elements to the left. Why do I get an error if I want to change the order from (x:xs)
to (xs:x)
?
shift n list@(x:xs)
| n == 0 = list
| otherwise = shift (n-1) (xs:x) -- (xs:x) error
Occurs check: cannot construct the infinite type: a ~ [a]
I don't know how to interpret this error. Maybe someone of you can help me. Thank you very much.
EDIT: As it was already mentioned, the correct term to use is rotate and not shift
Upvotes: 2
Views: 2400
Reputation: 15887
It's not the names that define the types, but the positions in a function call, or type declarations. If we check the type of :
, we see that the second argument is a list:
Prelude> :t (:)
(:) :: a -> [a] -> [a]
So pattern matching on this constructor will always give you the head
and tail
, not the init
and last
. Thus, using both x:xs
and xs:x
, you've declared that a
and [a]
are the same type, and the compiler balks. You could perform the append using e.g. xs ++ [x]
.
Data.Sequence has a type that does support both head and last patterns in the operators :<|
and |>:
.
In general, (singly linked) lists only make the head element efficiently accessible; operations like appending or even checking lengths are costly. When all you need is a stream or stack, they're great, but a long distance reordering operation like the rotation is better handled by a rope or circular buffer type. Either is available in Haskell, in e.g. container's Seq or ring-buffer's RingBuffer. Data.ByteString.Lazy and Data.Text.Lazy are also rope types.
Upvotes: 2
Reputation: 477170
Why do I get an error if I want to change the order from
(x:xs)
to(xs:x)
?
Because the types do not match. The type of (:)
is (:) :: a -> [a] -> [a]
. It thus expects an element x
(type a
) and a list with the rest of the elements (type [a]
). You can not just use (:)
in the opposite way.
You can use the (++) :: [a] -> [a] -> [a]
to concatenate two lists together. We can thus rotate to the left by dropping n
elements from the list and concatenating this with the first n
elements of the list to this.
rotateL :: Int -> [a] -> [a]
rotateL 0 list = list
rotateL n list | n < 0 = error "Negative index"
| otherwise = drop n list ++ take n list
or we can, like @YannVernier says, use splitAt :: Int -> [a] -> ([a], [a])
:
rotateL :: Int -> [a] -> [a]
rotateL 0 list = list
rotateL n list | n < 0 = error "Negative index"
| otherwise = lb ++ la
where (la, lb) = splitAt n list
or without mentioning the list
parameter:
rotateL :: Int -> [a] -> [a]
rotateL 0 = id
rotateL n | n < 0 = error "Negative index"
| otherwise= uncurry (flip (++)) . splitAt n
Note: based on how your attempt, I think you actually want to rotate the list to the left, not shift it, since that would mean that you simply
drop
the firstn
elements, and fill it perhaps with some extra value.
Note: in case
n
is larger than the length of the list, then therotateL
will act as an identity function. That might not be the desired behavior. I leave it as an exercise to fix this edge-case.
Upvotes: 6