TL;DR: the solution is at the end of the post.
You don't need the list decomposition pattern in your definition, just deal with the list as a whole through the list comprehension:
linsearch :: Eq a => a -> [a] -> Int
linsearch _ [] = -1
linsearch y xs = head [i | let j = length xs, i <- [0..j-1],
-- y == x -- x? what is that?
y == (xs!!i)]
Now linsearch 3 [0..9]
returns 3
, as it should. But linsearch 3 [0..]
doesn't return at all -- it is lost in trying to calculate the list's length, which isn't actually needed here at all! Moreover, ditching the length calculation makes us re-arrange the algorithm from its currently quadratic form into the much better, linear one:
linsearch :: Eq a => a -> [a] -> Int
linsearch _ [] = -1
linsearch y xs = head [i | (x,i) <- zip xs [0..], y == x]
linsearch 3 [0..]
now successfully returns 3
, as it should.
linsearch 3 [0,2..]
still diverges though (i.e. never returns), because Haskell doesn't know -- nor does it want to -- that there's no point searching on an ordered increasing list while past the very first element which is larger than the one we're searching for. That is so because [a]
is the type of lists, not of ordered lists.
We could define such variant though too, as e.g.
linsearchOrdered :: Ord a => a -> [a] -> Int
linsearchOrdered y xs = linsearch y $ takeUntil (> y) xs
takeUntil :: (a -> Bool) -> [a] -> [a]
takeUntil p xs = foldr (\x r -> if not (p x) then x:r else [x]) [] xs
And sure enough, it is working now:
> linsearchOrdered 3 [0,2..]
*** Exception: Prelude.head: empty list
> takeUntil (> 3) [0,2..]
[0,2,4]
it :: (Ord a, Num a, Enum a) => [a]
> linsearch 3 [0,2,4]
*** Exception: Prelude.head: empty list
wait, what? Where's the error coming from? It comes from your use of head
: since there were no 3
found in [0,2,4]
, you code calls head []
which is an error.
Instead, we can use take 1
, and convert its result to a Maybe a
with a standard use of listToMaybe
:
import Data.Maybe
linsearch :: (Eq a) => a -> [a] -> Maybe Int
linsearch y xs = listToMaybe $ take 1 [i | (x,i) <- zip xs [0..], y == x]
Ok, now it's working:
> linsearchOrdered 3 [0,2..]
Nothing
> linsearchOrdered 4 [0,2..]
Just 2
Note the return type is different now. Instead of using a special value, we use a wrapping type to indicate the success (with Just
) or failure (with Nothing
):
data Maybe a = Nothing | Just a
If you really want your original design, we can code it as
linsearch y xs = head $ [i | (x,i) <- zip xs [0..], y == x] ++ [-1]
Haskell is lazy so head
will (safely, now, with the ++ [-1]
) stop on the first matching element (if at all possible). There's almost no duplicated effort going to waste, both with using head
and takeUntil
.
Both are folds though. zip
can be coded as a fold as well, so linsearch
is a fold too. And folds composition can be fused into one fold by fusing the reducer functions, turning it all into an explicitly one-pass algorithm, if so desired.