Reputation: 5353
I was having a look at some list operations and came across !!
:
(!!) :: [a] -> Int -> a
xs !! n
| n < 0 = negIndex
| otherwise = foldr (\x r k -> case k of
0 -> x
_ -> r (k-1)) tooLarge xs n
The function (\x r k -> ...)
has type a -> (Int -> a) -> Int -> a
, but foldr
takes a function that should only accept two arguments:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr k z = go
where
go [] = z
go (y:ys) = y `k` go ys
Can someone explain to me why foldr
accepts a function that takes 3 arguments with the following type a -> (Int -> a) -> Int -> a
? Especially since the result should have the same type as the second argument?
Upvotes: 7
Views: 785
Reputation: 1092
(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
(!!)
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
.
Upvotes: 2
Reputation: 105935
->
is right-associative. So a -> b -> c
is a -> (b -> c)
. Therefore, your type
a -> (Int -> a) -> Int -> a
is the same as
a -> (Int -> a) -> (Int -> a)
and we can see that it fits foldr
's type quite well.
Upvotes: 3