Hmm, wouldn't this be the same as an infinite loop in any programming language? How do you solve an infinite loop in python for instance?
How would you solve an iterator in java producing infinite values?
If anything, the performance of a lazy language would be better than a strict one in the presence of an infinite sequence, as it gives you the opportunity to stop sooner and not enter in an infinite loop.
Example in haskell:
main = do
let a = [1..] -- infinite list
print (a !! 0) -- only prints the first element and exits
Example in python:
def f():
a = []
b = 0
for True:
a.append(b++)
return a
def g():
print f()[0] # Takes infinite time
> Hmm, wouldn't this be the same as an infinite loop in any programming language? How do you solve an infinite loop in python for instance?
In practice an infinite loop in Python is often an immediate error (stack overflow) at the point where it's declared. In cases like you showed it's a little fiddlier, but any execution of that code will immediately show that there's an infinite loop (e.g. any unit test of it will time out) and a stack dump taken while the program is "stuck" will show exactly where the infinite loop is (which is therefore where the error is, because an infinite loop is always an error in these languages). Which is still not an ideal situation, mind; I'm very much in favour of an Idris-style language where function termination is actually checked by the type system and there's a type-level distinction between data and codata.
> How would you solve an iterator in java producing infinite values?
I'd avoid ever passing around a bare iterator, ideally with a lint rule.
The point isn't that it's impossible to have lazy values in other languages. It's that it's practical and idiomatic to avoid them, or at least limit them to cases where you absolutely need them.
Just an anecdote, but once introduced an infinite loop in a golang program by forgetting to decrement a value inside some retry logic.
The effects were only visible in production, as it was the only environment where the error condition happened. It caused thousands and thousands of messages to be queued in vain.
It took me, and another person 3 days to review the code line by line to understand where the problem was.
As you said, any execution of the code would show that there is a problem. Just that un this case no one new where it was executing from.
I think you should drop the argument that performance with a lazy language is difficult to reason in terms of time. It is well studied that asymptotically, strict and non-strict languages are the same.
It is a different story, and a valid argument for Memory consumption, though. It seems to me that you read the “impossible to reason about” meme and formed your own opinion of why that would be so. But this is not the actual reason.
If you have an error-handling path that you don't test, that has the potential to cause a major production bug in virtually any language, Haskell included. Is there any reason to believe you wouldn't have had exactly the same problem in Haskell? Laziness can mean a pure compute loop isn't a problem, sure, but if you had a loop that was queuing messages that sounds like something that would be in an IO that got sequenced.
If you write the same code in a strict language or a lazy language, the asymptotic performance will be the same, sure. But that ignores the fact that people don't. Lazy languages use different idioms (there would be little point in laziness as a language feature if people didn't write code that relied on it), which is what results in code whose performance is impossible to reason about.
There's a lot I don't like about Go, and plenty of things to be cautious about in message-queue-based systems; laziness is by no means the only thing I worry about. "decrementing a counter" suggests the kind of imperative control flow I would be very cautious about; I favour iterating/recursing over data structures, which is inherently safe against infinite loops (but only in strict languages with immutable datastructures!). I can't remember the last time I decremented a counter that was being used for a loop termination condition in the code itself; I've occasionally written functions like "run this block n times", but the most trivial test of such functions tells you whether they're infinite loops or not. Of course this is only really practical in a language powerful enough to write recursion-schemes style constructions.
How would you solve an iterator in java producing infinite values?
If anything, the performance of a lazy language would be better than a strict one in the presence of an infinite sequence, as it gives you the opportunity to stop sooner and not enter in an infinite loop.
Example in haskell:
Example in python: