7

Are there functional languages that can specify, in the typechecker, whether or not a certain computation is guaranteed to terminate? Alternatively, can you do this in just Haskell?

Regarding Haskell, in this answer the poster says that

The usual way of thinking about this is that every Haskell type is "lifted"--it contains ⊥. That is, Bool corresponds to {⊥, True, False} rather than just {True, False}. This represents the fact that Haskell programs are not guaranteed to terminate and can have exceptions.

On the other hand, this paper about Agda states that

A correct Agda program is one which passes both type-checking and termination-checking

I.e., all Agda programs will terminate, and a Bool in Agda corresponds to exactly {True, False}.

So for example, in Haskell you can have a value of type IO a, which tells the typechecker that IO is needed to compute the value in question. Could you have a type Lifted a which asserts that the computation in question may not terminate? That is, you allow non-termination, but separate it in the type system. (Obviously, as in Agda, you can only separate values into "definitely terminates" and "might not terminate") If not, are there languages that do this?

Community
  • 1
  • 1
Dan
  • 12,409
  • 3
  • 50
  • 87
  • 2
    I don't know of very many total programming languages. http://en.wikipedia.org/wiki/Total_functional_programming. I can think of: Agda; Coq; Datalog; some variants of SQL SELECT statements (in the absence of functions and recursive Common Table Expressions). Note that all of these languages are not Turing complete. I'd personally love to have a functional programming language where Turing completion (non-termination) and exception handling are optional features similar to side effects by IO in Haskell. – Cirdec Oct 18 '13 at 17:20
  • That link you provided mentioned "corecursion" and "codata;" maybe that's what I'm looking for? I'm not sure. – Dan Oct 18 '13 at 17:33
  • @Cirdec That is such an interesting comment. Is it possible, by any chance, that you can turn your comment into a real answer to this question where you elaborate a little bit on how you can create total functions if you forego turing completeness? – kqr Oct 18 '13 at 17:36
  • @kqr This is what Agda does. The details of how this works in Agda aren't really relevant to _my_ question, but probably a good subject for a new question. – Dan Oct 18 '13 at 17:51
  • 1
    Why are you discounting Coq and Agda? I'm pretty sure people have invented plenty of tricks for representing and dealing with non-terminating terms in those languages (e.g. by turning a possibly non-terminating function into a definitely terminating one that sometimes returns "not finished yet, run me for a little longer" instead of an actual answer). – Daniel Wagner Oct 18 '13 at 22:56
  • 1
    Agda includes the [partiality monad](http://www.cse.chalmers.se/~nad/listings/lib-0.7/Category.Monad.Partiality.html) in its standard library, which can be thought of as your `Lifted` type constructor. – Cactus Oct 25 '13 at 02:49

1 Answers1

4

You certainly could. However it wouldn't be perfect. By definition, some computations which are terminate would have to reside in Lifted. This is called the halting problem.

Now before you give up on this idea, this isn't as bad is at sounds. Coq, Agda, and many others all work just fine with heuristics for checking termination.

The languages where this actually matters are in ones like Coq and Agda where you're trying to prove theorems. For example, let's say we have the type

 Definition falsy := exists n, n > 0 /\ 0 > n.
 -- Read this as, 
 --   "The type of a proof that for some number n, n > 0 and n < 0"

In Coq syntax. /\ means and. Now to prove such a property in Coq or Agda, we'd have to write something like

Definition out_proof : falsy = ____.
-- Read this as
--   "A proof that there exists a number n < 0 and n > 0"

Where ____ is a proof that for some number n, n > 0 and 0 > n. Now this is awfully hard, since well, falsy is false. Clearly no number exists that is both less than and greater than 0.

However, if we allowed nontermination with unbounded recursion,

 Definition bottom_proof : falsy = bottom_proof.

This type checks, but is obviously inconsistent! We just proved something false! Thus theorem proving assistants enforce some form of termination checking, otherwise they're worthless.

If you wanted to be pragmatic, you could use this lifted type to basically tell the typechecker, "Back off, I know this may not terminate but I'm ok with it". Which is helpful for writing real world stuff like say, a webserver or whatever where you might want it to run "forever".

In essence you're proposing a divide in the language, on one hand, you have "verified code" that you can safely prove things about, and on the other you have "unsafe code" which could loop forever. You're right with the comparison to IO. This is exactly the same idea as Haskell's partitioning of side effects.

Edit: Corecursive data

You mentioned corecursive data, but this isn't quite what you want. The idea is that you do loop forever, but you do so "productively". In essence with recursion the simplest method of checking you terminate is that you always recurse with a term strictly smaller than what you currently have.

Fixpoint factorial n := match n with
  | 0 => 1
  | S n' => n * factorial n'

With corecursion your resulting term must be "bigger" than your input.

Cofixpoint stream := Cons 1 stream

Again this doesn't allow for

 Cofixpoint stream := stream
daniel gratzer
  • 52,833
  • 11
  • 94
  • 134
  • The requirement for total functions doesn't mean you can't write webservers which run forever. Codata is actually pretty good at that sort of thing. (also, minor point, I think your use of "By definition" near the start is unfair. The undecidability of the halting problem is definitely a nontrivial theorem.) – Ben Millwood Dec 06 '13 at 20:59