views:

460

answers:

4

I have some Haskell code that does work correctly on an infinite list, but I do not understand why it can do so successfully. (I modified my original code -- that did not handle infinite lists -- to incorporate something from some other code online, and suddenly I see that it works but don't know why).

myAny :: (a -> Bool) -> [a] -> Bool
myAny p list = foldr step False list
   where
      step item acc = p item || acc

My understanding of foldr is that it will loop through every item in the list (and perhaps that understanding is incomplete). If so, it should not matter how the "step" function is phrased ... the code should be unable to handle infinite loops.

However, the following works:

*Main Data.List> myAny even [1..]
True

Please help me understand: why??

+1  A: 

The key point here is that Haskell is a non-strict language. "Non-strict" means that it allows non-strict functions, which in turn means that function parameters may not be fully evaluated before they may be used. This obviously allows for lazy evaluation, which is "a technique of delaying a computation until the result is required".

Start from this Wiki article

Anton Gogolev
OK, I knew that about lazy evaluation. But I need help connecting the dots from that to why the above code works. Meanwhile, I'll check out the wiki article. Thanks.
Charlie Flowers
A: 

I do not know Haskell, but I suspect that in your case, it works because of lazy evaluation. Because it allows you to work with list infinitely long, when you access it, it will compute the result as you need it.

See http://en.wikipedia.org/wiki/Lazy_evaluation

Hao Wooi Lim
That's a good article, thanks for the link.
Charlie Flowers
+22  A: 

Let's do a little trace in our heads of how Haskell will evaluate your expression. Substituting equals for equals on each line, the expression pretty quickly evaluates to True:

myAny even [1..]
foldr step False [1..]
step 1 (foldr step False [2..])
even 1 || (foldr step False [2..])
False  || (foldr step False [2..])
foldr step False [2..]
step 2 (foldr step False [3..])
even 2 || (foldr step False [3..])
True   || (foldr step false [3..])
True

This works because acc is passed as an unevaluated thunk (lazy evaluation), but also because the || function is strict in its first argument.

So this terminates:

True || and (repeat True)

But this does not:

and (repeat True) || True

Look at the definition of || to see why this is the case:

True  || _ =  True
False || x =  x
Apocalisp
Additionally you can verify that the code doesn't compute more than 2 elements with: `myAny p list = foldr (\i a -> trace (show i) (p i || a))` - that will show only `1 2 True`
viraptor
Wow, this has been a VERY eye opening response. First of all, I didn't start out with the definition of foldr in front of me. I assumed its code would use advanced features I don't know yet, so I was only going to look at it as last resort. Your response prompted me to take a look, and that clarifies a lot. foldr itself is using "plain old" structural recursion. I love the way you broke it down. Thanks.
Charlie Flowers
BTW, is the || fully strict in its first arg, or does it merely "give preference to" its first arg? For example, what if arg 2 had already been eval'd, but arg 1 was still just a thunk? And say arg 2 was false. Would Haskell short-circuit in the opposite direction? Thanks.
Charlie Flowers
Yes, I believe || is fully strict in the first argument. A call to it can't be evaluated without having a fully evaluated Bool on at least one side. Just have a look at the source for ||:True || _ = TrueFalse || x = x
Apocalisp
If you want a "symmetric" || that will terminate if *either* side is True, take a look at the `lub` package on Hackage.
Porges
+5  A: 

My understanding of foldr is that it will loop through every item in the list (and perhaps that understanding is incomplete).

foldr (unlike foldl) does not have to loop through every item of the list. It is instructive to look at how foldr is defined.

foldr f z []     = z
foldr f z (x:xs) = f x (foldr f z xs)

When a call to foldr is evaluated, it forces the evaluation of a call to the function f. But note how the recursive call to foldr is embedded into an argument to the function f. That recursive call is not evaluated if f does not evaluate its second argument.

newacct
Good point. I initially did not know the foldr definition was going to be understandable at my level of Haskell knowledge. I notice how you point out that the recursive call was intentionally embedded into a function argument, in order to make it a thunk. Is that something that Haskell dev's find themselves doing a lot? Are there cases where you don't need a function, but you create one merely so you can pass an argument and know it will be a thunk?
Charlie Flowers