(more explanation for others ;)
(!!) :: [a] -> Int -> a
xs !! n
| n < 0 = negIndex
| otherwise = foldr (\x r k -> case k of
0 -> x
_ -> r (k-1)) tooLarge xs n
foldr :: (a -> b -> b) -> b -> [a] -> b
-- ^1 ^2
foldr commonly makes an accumulated(?) value. In this case, foldr makes an
accumulated function (b) of the type (Int -> a)! foldr ... tooLarge xs is evaluated to an
accumulated function, and this accumulated function (^2) takes an argument n. ^1 is a tooLarge function. Interestingly, the buildup of this
accumulated function depends on the value of a free variable n (i.e., k).
For example, ['a', 'b', 'c'] !! 2 is evaluated like below:
\x r k = \'a' r 2 -> r (2-1) (r is not known yet, and drives further evaluations.)
\x r k = \'b' r 1 -> r (1-1)
\x r k = \'c' r 0 -> 'c'
['a', 'b', 'c'] !! 3 goes like this:
\x r k = \'a' r 3 -> r (3-1)
\x r k = \'b' r 2 -> r (2-1)
\x r k = \'c' r 1 -> r (1-1) (r turns out to be tooLarge.) = tooLarge (1-1) (ERROR!)
You can check debug traces:
module Main where
import Debug.Trace
tooLarge _ = errorWithoutStackTrace "!!!: index too large"
negIndex = errorWithoutStackTrace "!!!: negative index"
(!!!) :: Show a => [a] -> Int -> a
xs !!! n
| n < 0 = negIndex
| otherwise = foldr (\x r k -> trace ("x: " ++ show x ++ ", k: " ++ show k) $
case k of
0 -> x
_ -> r (k-1)) tooLarge xs n
main = do
print $ ['a', 'b', 'c'] !!! 2
print $ ['a', 'b', 'c'] !!! 3
-- x: 'a', k: 2
-- x: 'b', k: 1
-- x: 'c', k: 0
-- 'c'
-- x: 'a', k: 3
-- x: 'b', k: 2
-- x: 'c', k: 1
-- sample: !!!: index too large
This
(!!) implementation is a report version. The report version of the prelude is more efficient than a familiar naive recursive implementation,
due to optimizations of
foldr.