There are languages other than Haskell, such as Coq, which banned bottom, or undefined, or infinite recursive definitions like
bot :: forall a. a
bot = bot
The benefit of not having bottom is simple : all programs terminate. The compiler guarantees that there are no infinite loops, no infinite recursions.
There is also a less obvious benefit : the logic of the language (given by the Curry-Howard correspondence) is consistent, it cannot prove a contradiction. So the same language can write both programs and proofs that the programs are correct. But that's maybe off-topic here.
The protection against infinite recursions is also simple : force each recursive definition to have arguments (here bot has none) and force recursive calls to be decreasing on one of those arguments. Here decreasing is in the sense of algebraic data types, seen as finite trees of contructors and values. Coq's compiler checks that the decreasing argument is an ADT (data in Haskell) and that the recursive calls are done on subtrees of the argument, typically via a case of, not on other trees coming from somewhere else.
Now the cost of this language constraint : we lose Turing-completeness (because we cannot solve the halting problem). That means there are terminating functions, possible to code in Haskell using general recursions, that would become refused by the compiler. In practice however, the magnitude of Coq's library shows that those exotic functions are rarely needed. Does someone even know one of them ?
There are cases where infinite loops make sense :
- Interactive programs, where the user issues commands by clicking or typing on the keyboard, usually run forever. They wait for a command, process it and then wait for the next command. Until the end of time, or more seriously until the user issues the quit command.
- Likewise, instead of processing an infinite stream of user commands, process an infinite stream of data. Such as continuous queries on a database.
Those cases are rather specific and might be treated by new language primitives. Haskell introduced IO to trace unsafe interactions. Why not declare the possibility of infinite loops in the signature of functions ? Or split a complex program into a DSMS that calls Haskell functions for pure computations ?
EDIT
Here is an algorithm example, that might clarify what changes if we switch to total programming. Euclid's algorithm for computing the GCD of 2 numbers, first in plain recursive Haskell
euclid_gcd :: Int -> Int -> Int
euclid_gcd m n = if n <= 0 then m else euclid_gcd n (m `mod` n)
Two things can be proven concerning this function : that it terminates, and that it does compute the GCD of m and n. In a language accepting proof scripts, we would give the compiler a proof that (m mod n) < n, so that it concludes the recursion is decreasing on its second argument, and therefore terminates.
In Haskell I doubt we can do that, so we can try to rewrite this algorithm in a structural recursive form, that the compiler can easily check. That means a recursive call must be done on the predecessor of some argument. Here m mod n won't do, so it looks like we are stuck. But as with tail recursion, we can add new arguments. If we find a bound on the number of recursive calls, we are done. The bound does not have to be precise, it just needs to be above the actual number of recursive calls. Such a bound argument is usually called variant in the literature, I personally call it fuel. We force the recursion to stop with an error value when it runs out of fuel. Here we can take the successor of any of the 2 numbers :
euclid_gcd_term :: Int -> Int -> Int
euclid_gcd_term m n = euclid_gcd_rec m n (n+1)
where
euclid_gcd_rec :: Int -> Int -> Int -> Int
euclid_gcd_rec m n fuel =
if fuel <= 0 then 0
else if n <= 0 then m else euclid_gcd_rec n (m `mod` n) (fuel-1)
Here the termination proof somewhat leaks into the implementation, making it slightly harder too read. And the implementation makes useless computations on the fuel argument, which could slow down a bit, though in this case I hope Haskell's compiler will make it negligible. Coq has an extraction mechanism, that erases the proof part of such mixes of proofs and program, to produce OCaml or Haskell code.
As for euclid_gcd we would then need to prove that euclid_gcd_term does compute the GCD of n and m. That includes proving Euclid's algorithm terminates in less than n+1 steps.
euclid_gcd_term is obviously more work than euclid_gcd and arguably less fun. On the other hand, once the habit is taken, I find it rewarding intellectually to know bounds for my algorithms. And when I cannot find such bounds, it usually means I don't understand my algorithms. Which also usually means they are bugged. We cannot force all developers to use total programming for all programs, but wouldn't a compiling option in Haskell to do it on demand be nice?
Delay-ed result, pressingCtrl-cto abort the computation when they get bored. Exactly like in Haskell / Ocaml. Except that here the type is being honest about the fact that you may have to wait forever for an answer that may never come. - gallais