Is there a way to tell if a list in Haskell is infinite? The reason is that I don't want to apply functions such as length
to infinite lists.

- 724
- 6
- 18
-
1Concerning you question "don't infinite structures make my programs very vulnerable" in the comments: yes and no. You may as well put it another way: algorithms that rely on your structures to be finite make yor programs very vulnarable. But actually, both are completely ok if handled seperately. Errors are usually easy to detect by means of _simple_ test programs: in such a program, structures will be either very small (like 3 elements => program should finish really quickly) or infinite. – leftaroundabout Sep 10 '11 at 16:55
6 Answers
Applying length
to unknown lists is generally a bad idea, both practically due to infinite lists, and conceptually because often it turns out that you don't actually care about the length anyway.
You said in a comment:
I'm very new to Haskell, so now, don't infinite structures make my programs very vulnerable?
Not really. While some of us wish there were better ways to distinguish between necessarily finite and necessarily infinite data, you're always safe when you create, process, and examine lazy structures incrementally. Computing the length is clearly not incremental, but checking to see if the length is above or below some cut-off value is, and very often that's all you wanted to do anyway!
A trivial case is testing for nonempty lists. isNonEmpty xs == length xs > 0
is a bad implementation because it examines an unbounded number of elements, when examining a single one would suffice! Compare this:
isNonEmpty [] = False
isNonEmpty (_:_) = True
Not only is this is safe to apply to an infinite list, it's also much more efficient on finite lists--it takes only constant time, instead of time linear in the length of the list. It's also how the standard library function null
is implemented.
To generalize this for length testing relative to a cut-off, you'll obviously need to examine as much of the list as the length you're comparing to. We can do exactly this, and no more, using the standard library function drop
:
longerThan :: Int -> [a] -> Bool
longerThan n xs = isNonEmpty $ drop n xs
Given a length n
and a (possibly infinite) list xs
, this drops the first n
elements of xs
if they exist, then checks to see if the result is non-empty. Because drop
produces the empty list if n
is larger than the length of the list, this works correctly for all positive n
(alas, there's no non-negative integer type, e.g. natural numbers, in the standard libraries).
The key point here is that it's better in most cases to think of lists as iterative streams, not a simple data structure. When possible you want to do things like transform, accumulate, truncate, etc., and either produce another list as output or examine only a known-finite amount of the list, rather than trying to process the entire list in one go.
If you use this approach, not only will your functions work correctly on finite and infinite lists both, but they'll also benefit more from laziness and GHC's optimizer, and be likely to run faster and use less memory.

- 6,754
- 24
- 45

- 76,893
- 19
- 209
- 302
-
"Computing the length is clearly not incremental, but checking to see if the length is above or below some cut-off value is" -- to be clear, `if length xs < n then ...` is the wrong way to do that :) – Tyler Sep 12 '11 at 17:10
The Halting Problem was first proved unsolvable by assuming a Halting Oracle existed, then writing a function that did the opposite of what that oracle said would happen. Let's reproduce that here:
isInfinite :: [a] -> Bool
isInfinite ls = {- Magic! -}
Now, we want to make a list impossibleList
that does the opposite of what isInfinite
says it should. So, if impossibleList
is infinite, it is actually []
, and if it isn't infinite, it is something : impossibleList
.
-- using a string here so you can watch it explode in ghci
impossibleList :: [String]
impossibleList =
case isInfinite impossibleList of
True -> []
False -> "loop!" : impossibleList
Try this out yourself in ghci with isInfinite = const True
and isInfinite = const False
.
-
18There's nothing quite like a diagonalization argument to ruin everything forever. I blame Cantor for starting the whole thing. – C. A. McCann Sep 10 '11 at 16:42
-
1Hmm, but suppose `isInfinite` were strict (just regular strict, not spine-strict)... that would be reasonable right? Then `impossibleList` is just `_|_` and there is no contradiction. – luqui Sep 11 '11 at 01:08
-
10Can't you use a similar "proof" to show that it's impossible to check if a list is non-empty? Just replace `isInfinite` with `isNonEmpty`. But `isNonEmpty` can obviously be implemented. – interjay Sep 11 '11 at 09:09
-
1@interjay is right, this is just a circular definition... using any strict function of type `[a] -> Bool` in place of `isInfinite` would make `impossibleList` = ⊥. I'm not sure what this proves beyond that Haskell is Turing complete :P – Tom Crockett Sep 14 '11 at 09:02
-
1@luqui: What do you think ⊥ means, then? Encoding logical contradictions via the Curry-Howard correspondence typically leads to nontermination, and the whole point of non-strict semantics is to "not run away from paradoxes" and allow contradictions to occur as long as they're irrelevant to the end result. – C. A. McCann Sep 14 '11 at 12:46
-
@pelotom: Isn't that exactly the point? That's what diagonalization arguments do. This just shows that no possible implementation of `isInfinite` can work on all lists. It doesn't say anything about implementations that work for some inputs, and further argument would be needed to show that, in pure code, the implementation in sclv's answer is the best possible. – C. A. McCann Sep 14 '11 at 12:52
-
I think this answer is right this question is an instance of the halting problem (?) but the example given makes no sense to me if it defines the function impossibleList in terms of properties of impossibleList -- the proof of the halting problem is showing HOW to do that. Surely the correct analogy is to define isInfinite in terms of a list defined in terms of one parameter, and that parameter. Then define impossibleList(s) = isInfinite(s,s) ? [] : infiniteList. Then ask what is isIfinite(impossibleList, impossibleList)? Then you have a contradiction, but only with the quine trick. – Jack V. Sep 14 '11 at 13:58
-
2@C. A. McCann: This code doesn't show that "no possible implementation of `isInfinite` can work on all lists", because `impossibleList` is not a list (it is ⊥). Otherwise you could say that it also shows that no possible implementation of `isNonEmpty` can work on all lists, as I said in my previous comment. – interjay Sep 14 '11 at 17:57
-
@interjay: It doesn't really make sense to say that ⊥ is not a list, that's confusing values with types. And yes, the only functions defined on all inputs are those of the form `const x` for some defined `x`. – C. A. McCann Sep 14 '11 at 18:13
-
@Jack V.: Yes, that's a more explicit (and probably better) encoding. The answer's version is relying on syntactic recursion to do that implicitly, which obfuscates matters somewhat. – C. A. McCann Sep 14 '11 at 18:16
-
@pelotom: Exactly the way you said earlier. It's a fairly trivial demonstration of how the Halting problem can be proven undecidable. It doesn't say anything significant about `isInfinite` specifically, but given Turing-completeness it's trivial to construct lists with diverging tails at any point. You'd further need some sort of inductive argument to show that sclv's implementation is the best possible. – C. A. McCann Sep 14 '11 at 18:29
-
3@C. A. McCann: Not only does this not say anything about `isInfinite`, it also says nothing about decidability or the halting problem. Consider functions of the form `f::Int32->Bool`. Since there is a finite number of possible inputs, they are all decidable. But it would be easy to use a construction almost identical to the above to supposedly "prove" that any non-trivial `f` cannot be computed for all values. For example, for `f x = x>0`, use `impossibleValue = if (f impossibleValue) then 0 else 1` – interjay Sep 14 '11 at 19:42
-
7@C. A. McCann: The premise is that `isInfinite` decides the infiniteness of a given list, i.e. it returns `True` for any infinite list, and `False` for any finite list... what it does if you give it ⊥ is neither here nor there. We can only derive a contradiction by passing it a finite list and showing that it _doesn't_ return `False` (evaluates to `True` or ⊥), or by passing it an infinite list and showing that it _doesn't_ return `True` (evaluates to `False` or ⊥). Giving it ⊥ and showing that it produces ⊥ only shows that `isInfinite` is strict. – Tom Crockett Sep 14 '11 at 20:50
-
1-1. Wrong: The halting problem is about examining a program **text** to decide whether it terminates or not. Haskell is not reflective, there is **no** way to examine a list's **definition** (like `"[1..]"`), only the value `[1,2,3,...]` produced by it, whether packaged up in a thunk or not. – Will Ness Mar 30 '12 at 23:21
-
-1 from me too, peletom and interjay both have entirely reasonable and entirely fatal criticisms here. This argument is faulty to the point of being misleading. – Ben Millwood May 12 '12 at 16:41
We don't need to solve the Halting Problem to call 'length' safely. We just need to be conservative; accept everything that has a finiteness proof, reject everything that doesn't (including many finite lists). This is exactly what type systems are for, so we use the following type (t is our element type, which we ignore):
terminatingLength :: (Finite a) => a t -> Int
terminatingLength = length . toList
The Finite class will only contains finite lists, so the type-checker will ensure we have a finite argument. membership of Finite will be our proof of finiteness. The "toList" function just turns Finite values into regular Haskell lists:
class Finite a where
toList :: a t -> [t]
Now what are our instances? We know that empty lists are finite, so we make a datatype to represent them:
-- Type-level version of "[]"
data Nil a = Nil
instance Finite Nil where
toList Nil = []
If we 'cons' an element on to a finite list, we get a finite list (eg. "x:xs" is finite if "xs" is finite):
-- Type-level version of ":"
data Cons v a = Cons a (v a)
-- A finite tail implies a finite Cons
instance (Finite a) => Finite (Cons a) where
toList (Cons h t) = h : toList t -- Simple tail recursion
Anyone calling our terminatingLength function must now prove that their list is finite, otherwise their code won't compile. This hasn't removed the Halting Problem issue, but we have shifted it to compile-time rather than run-time. The compiler may hang while trying to determine membership of Finite, but that's better than having a production program hang when it's given some unexpected data.
A word of caution: Haskell's 'ad-hoc' polymorphism allows pretty much arbitrary instances of Finite to be declared at other points in the code, and terminatingLength will accept these as finiteness proofs even if they're not. This isn't too bad though; if someone's trying to bypass the safety mechanisms of your code, they get the errors they deserve ;)

- 131
- 1
- 2
-
For the inspiration behind this, take a look at Conor McBride's "Faking It: Simulating Dependent Types in Haskell" – Warbo Sep 19 '12 at 13:10
isInfinite x = length x `seq` False

- 38,665
- 7
- 99
- 204
-
I don't get it, with this definition `isInfinite [1,2,3]`gives `False`, and `isInfinite [1..]` also gives `False`. – alexkelbo Sep 11 '11 at 12:53
-
13@alexraasch: No, the `seq` there ensures that it computes the length--which of course will never finish if the list is infinite. So it either gives `False` if the list is finite (which is correct), or it never completes and thus never answers at all. And an answer that doesn't exist can't be called *incorrect*, can it? :] And yes, it's a joke, if that wasn't obvious. – C. A. McCann Sep 12 '11 at 04:56
No - you may at best estimate. See the Halting Problem.

- 5,757
- 4
- 25
- 55
-
1Ok, that's sad. Didn't realize it was an instance of the Halting Problem. – alexkelbo Sep 10 '11 at 13:09
-
well, it is about to tell, if your program will run indefinetly - which would be true for an infinite list. Maybe someone else has a better answer for you. – Lars Sep 10 '11 at 13:27
-
Really? Do you have a reduction from the halting problem? How do you claim that this problem is as hard as the halting problem? (Of course there's the obvious reduction *to* the halting problem—you can view this as an instance of the halting problem—but that's the wrong direction and doesn't mean anything.) You need to know that if you could check whether a list was infinite, then you could also solve the halting problem. – ShreevatsaR Sep 10 '11 at 13:51
-
7well, you could create a list that returns the current element on the tape in the turing machine where the head points to. if you can say whether the list is finite you can solve the halting problem. – Karoly Horvath Sep 10 '11 at 14:03
-
@yi_H: so your model is that the list is being fed to the Haskell program by an oracle? (In that case you don't even need to appeal to the halting problem; you can just imagine an adversary that waits for the Haskell program to announce an output and decide that the list is the opposite type.) – ShreevatsaR Sep 10 '11 at 15:44
-
I don't understand what you mean by "a list that returns an element". A list doesn't return anything. – alexkelbo Sep 10 '11 at 15:46
-
4@Alexander Raasch: No Oracle. You build a turing machine simulator in Haskell and it provides a list which shows how the head moves. End of the list: program finished. – Karoly Horvath Sep 10 '11 at 15:48
-
Ah, ok. I'm very new to Haskell, so now, don't infinite structures make my programs very vulnerable? – alexkelbo Sep 10 '11 at 15:55
-
8@ShreevatsaR: Take an arbitrary general recursive function, rewrite it as an unfold operation that produces a list of successive values taken at each recursive step and a singleton list when reaching the base case. The list will be finite iff the original function terminates, but arbitrary general recursive functions are exactly those computable by a Turing machine. So an `isFinite` function that works on arbitrary lists is necessarily a Halting oracle. – C. A. McCann Sep 10 '11 at 16:40
There is also the possibility of separating finite and infinite lists by design and use distinct types for them.
Unfortunately Haskell (unlike Agda for example) doesn't allow you to enforce that a data structure is always finite, you can employ techniques of total functional programming to ensure that.
And you can declare infinite lists (AKA streams) as
data Stream a = Stream a (Stream a)
which doesn't have any way how to terminate a sequence (it's basically a list without []
).

- 62,528
- 13
- 153
- 317