on f572cd0 help sign in
Idris-dev/docs/tutorial/theorems.rst
444 lines 14 kB history raw
1.. _sect-theorems:
3===============
4Theorem Proving
5===============
7Equality
8--------
10allows propositional equalities to be declared, allowing theorems about
11programs to be stated and proved. Equality is built in, but conceptually
12has the following definition:
14.. code-block:: idris
16 data (=) : a -> b -> Type where
17 Refl : x = x
19Equalities can be proposed between any values of any types, but the only
20way to construct a proof of equality is if values actually are equal.
21For example:
23.. code-block:: idris
25 fiveIsFive : 5 = 5
26 fiveIsFive = Refl
28 twoPlusTwo : 2 + 2 = 4
29 twoPlusTwo = Refl
31.. _sect-empty:
33The Empty Type
34--------------
36There is an empty type, :math:`\bot`, which has no constructors. It is
37therefore impossible to construct an element of the empty type, at least
38without using a partially defined or general recursive function (see
39Section :ref:`sect-totality` for more details). We can therefore use the
40empty type to prove that something is impossible, for example zero is
41never equal to a successor:
43.. code-block:: idris
45 disjoint : (n : Nat) -> Z = S n -> Void
46 disjoint n p = replace {P = disjointTy} p ()
47 where
48 disjointTy : Nat -> Type
49 disjointTy Z = ()
50 disjointTy (S k) = Void
52There is no need to worry too much about how this function works —
53essentially, it applies the library function ``replace``, which uses an
54equality proof to transform a predicate. Here we use it to transform a
55value of a type which can exist, the empty tuple, to a value of a type
56which can’t, by using a proof of something which can’t exist.
58Once we have an element of the empty type, we can prove anything.
59``void`` is defined in the library, to assist with proofs by
60contradiction.
62.. code-block:: idris
64 void : Void -> a
66Simple Theorems
67---------------
69When type checking dependent types, the type itself gets *normalised*.
70So imagine we want to prove the following theorem about the reduction
71behaviour of ``plus``:
73.. code-block:: idris
75 plusReduces : (n:Nat) -> plus Z n = n
77We’ve written down the statement of the theorem as a type, in just the
78same way as we would write the type of a program. In fact there is no
79real distinction between proofs and programs. A proof, as far as we are
80concerned here, is merely a program with a precise enough type to
81guarantee a particular property of interest.
83We won’t go into details here, but the Curry-Howard correspondence [1]_
84explains this relationship. The proof itself is trivial, because
85``plus Z n`` normalises to ``n`` by the definition of ``plus``:
87.. code-block:: idris
89 plusReduces n = Refl
91It is slightly harder if we try the arguments the other way, because
92plus is defined by recursion on its first argument. The proof also works
93by recursion on the first argument to ``plus``, namely ``n``.
95.. code-block:: idris
97 plusReducesZ : (n:Nat) -> n = plus n Z
98 plusReducesZ Z = Refl
99 plusReducesZ (S k) = cong (plusReducesZ k)
101``cong`` is a function defined in the library which states that equality
102respects function application:
104.. code-block:: idris
106 cong : {f : t -> u} -> a = b -> f a = f b
108We can do the same for the reduction behaviour of plus on successors:
110.. code-block:: idris
112 plusReducesS : (n:Nat) -> (m:Nat) -> S (plus n m) = plus n (S m)
113 plusReducesS Z m = Refl
114 plusReducesS (S k) m = cong (plusReducesS k m)
116Even for trival theorems like these, the proofs are a little tricky to
117construct in one go. When things get even slightly more complicated, it
118becomes too much to think about to construct proofs in this ‘batch
119mode’. therefore provides an interactive proof mode.
121Interactive theorem proving
122---------------------------
124Instead of writing the proof in one go, we can use ’s interactive proof
125mode. To do this, we write the general *structure* of the proof, and use
126the interactive mode to complete the details. We’ll be constructing the
127proof by *induction*, so we write the cases for ``Z`` and ``S``, with a
128recursive call in the ``S`` case giving the inductive hypothesis, and
129insert *metavariables* for the rest of the definition:
131.. code-block:: idris
133 plusReducesZ' : (n:Nat) -> n = plus n Z
134 plusReducesZ' Z = ?plusredZ_Z
135 plusReducesZ' (S k) = let ih = plusReducesZ' k in
136 ?plusredZ_S
138On running , two global names are created, ``plusredZ_Z`` and
139``plusredZ_S``, with no definition. We can use the ``:m`` command at the
140prompt to find out which metavariables are still to be solved (or, more
141precisely, which functions exist but have no definitions), then the
142``:t`` command to see their types:
144.. code-block:: idris
146 *theorems> :m
147 Global metavariables:
148 [plusredZ_S,plusredZ_Z]
150.. code-block:: idris
152 *theorems> :t plusredZ_Z
153 plusredZ_Z : Z = plus Z Z
155 *theorems> :t plusredZ_S
156 plusredZ_S : (k : Nat) -> (k = plus k Z) -> S k = plus (S k) Z
158The ``:p`` command enters interactive proof mode, which can be used to
159complete the missing definitions.
161.. code-block:: idris
163 *theorems> :p plusredZ_Z
165 ---------------------------------- (plusredZ_Z) --------
166 {hole0} : Z = plus Z Z
168This gives us a list of premises (above the line; there are none here)
169and the current goal (below the line; named ``{hole0}`` here). At the
170prompt we can enter tactics to direct the construction of the proof. In
171this case, we can normalise the goal with the ``compute`` tactic:
173.. code-block:: idris
175 -plusredZ_Z> compute
177 ---------------------------------- (plusredZ_Z) --------
178 {hole0} : Z = Z
180Now we have to prove that ``Z`` equals ``Z``, which is easy to prove by
181``Refl``. To apply a function, such as ``Refl``, we use ``refine`` which
182introduces subgoals for each of the function’s explicit arguments
183(``Refl`` has none):
185.. code-block:: idris
187 -plusredZ_Z> refine Refl
188 plusredZ_Z: no more goals
190Here, we could also have used the ``trivial`` tactic, which tries to
191refine by ``Refl``, and if that fails, tries to refine by each name in
192the local context. When a proof is complete, we use the ``qed`` tactic
193to add the proof to the global context, and remove the metavariable from
194the unsolved metavariables list. This also outputs a trace of the proof:
196.. code-block:: idris
198 -plusredZ_Z> qed
199 plusredZ_Z = proof
200 compute
201 refine Refl
203.. code-block:: idris
205 *theorems> :m
206 Global metavariables:
207 [plusredZ_S]
209The ``:addproof`` command, at the interactive prompt, will add the proof
210to the source file (effectively in an appendix). Let us now prove the
211other required lemma, ``plusredZ_S``:
213.. code-block:: idris
215 *theorems> :p plusredZ_S
217 ---------------------------------- (plusredZ_S) --------
218 {hole0} : (k : Nat) -> (k = plus k Z) -> S k = plus (S k) Z
220In this case, the goal is a function type, using ``k`` (the argument
221accessible by pattern matching) and ``ih`` — the local variable
222containing the result of the recursive call. We can introduce these as
223premisses using the ``intro`` tactic twice (or ``intros``, which
224introduces all arguments as premisses). This gives:
226.. code-block:: idris
228 k : Nat
229 ih : k = plus k Z
230 ---------------------------------- (plusredZ_S) --------
231 {hole2} : S k = plus (S k) Z
233Since plus is defined by recursion on its first argument, the term
234``plus (S k) Z`` in the goal can be simplified, so we use ``compute``.
236.. code-block:: idris
238 k : Nat
239 ih : k = plus k Z
240 ---------------------------------- (plusredZ_S) --------
241 {hole2} : S k = S (plus k Z)
243We know, from the type of ``ih``, that ``k = plus k Z``, so we would
244like to use this knowledge to replace ``plus k Z`` in the goal with
245``k``. We can achieve this with the ``rewrite`` tactic:
247.. code-block:: idris
249 -plusredZ_S> rewrite ih
251 k : Nat
252 ih : k = plus k Z
253 ---------------------------------- (plusredZ_S) --------
254 {hole3} : S k = S k
256 -plusredZ_S>
258The ``rewrite`` tactic takes an equality proof as an argument, and tries
259to rewrite the goal using that proof. Here, it results in an equality
260which is trivially provable:
262.. code-block:: idris
264 -plusredZ_S> trivial
265 plusredZ_S: no more goals
266 -plusredZ_S> qed
267 plusredZ_S = proof {
268 intros;
269 rewrite ih;
270 trivial;
271 }
273Again, we can add this proof to the end of our source file using the
274``:addproof`` command at the interactive prompt.
276.. _sect-totality:
278Totality Checking
279-----------------
281If we really want to trust our proofs, it is important that they are
282defined by *total* functions — that is, a function which is defined for
283all possible inputs and is guaranteed to terminate. Otherwise we could
284construct an element of the empty type, from which we could prove
285anything:
287.. code-block:: idris
289 -- making use of 'hd' being partially defined
290 empty1 : Void
291 empty1 = hd [] where
292 hd : List a -> a
293 hd (x :: xs) = x
295 -- not terminating
296 empty2 : Void
297 empty2 = empty2
299Internally, checks every definition for totality, and we can check at
300the prompt with the ``:total`` command. We see that neither of the above
301definitions is total:
303::
305 *theorems> :total empty1
306 possibly not total due to: empty1#hd
307 not total as there are missing cases
308 *theorems> :total empty2
309 possibly not total due to recursive path empty2
311Note the use of the word “possibly” — a totality check can, of course,
312never be certain due to the undecidability of the halting problem. The
313check is, therefore, conservative. It is also possible (and indeed
314advisable, in the case of proofs) to mark functions as total so that it
315will be a compile time error for the totality check to fail:
317.. code-block:: idris
319 total empty2 : Void
320 empty2 = empty2
322::
324 Type checking ./theorems.idr
325 theorems.idr:25:empty2 is possibly not total due to recursive path empty2
327Reassuringly, our proof in Section :ref:`sect-empty` that the zero and
328successor constructors are disjoint is total:
330.. code-block:: idris
332 *theorems> :total disjoint
333 Total
335The totality check is, necessarily, conservative. To be recorded as
336total, a function ``f`` must:
338- Cover all possible inputs
340- Be *well-founded* — i.e. by the time a sequence of (possibly
341 mutually) recursive calls reaches ``f`` again, it must be possible to
342 show that one of its arguments has decreased.
344- Not use any data types which are not *strictly positive*
346- Not call any non-total functions
348Directives and Compiler Flags for Totality
349~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
351By default, allows all definitions, whether total or not. However, it is
352desirable for functions to be total as far as possible, as this provides
353a guarantee that they provide a result for all possible inputs, in
354finite time. It is possible to make total functions a requirement,
355either:
357- By using the ``--total`` compiler flag.
359- By adding a ``%default total`` directive to a source file. All
360 definitions after this will be required to be total, unless
361 explicitly flagged as ``partial``.
363All functions *after* a ``%default total`` declaration are required to
364be total. Correspondingly, after a ``%default partial`` declaration, the
365requirement is relaxed.
367Finally, the compiler flag ``--warnpartial`` causes to print a warning
368for any undeclared partial function.
370Totality checking issues
371~~~~~~~~~~~~~~~~~~~~~~~~
373Please note that the totality checker is not perfect! Firstly, it is
374necessarily conservative due to the undecidability of the halting
375problem, so many programs which *are* total will not be detected as
376such. Secondly, the current implementation has had limited effort put
377into it so far, so there may still be cases where it believes a function
378is total which is not. Do not rely on it for your proofs yet!
380Hints for totality
381~~~~~~~~~~~~~~~~~~
383In cases where you believe a program is total, but does not agree, it is
384possible to give hints to the checker to give more detail for a
385termination argument. The checker works by ensuring that all chains of
386recursive calls eventually lead to one of the arguments decreasing
387towards a base case, but sometimes this is hard to spot. For example,
388the following definition cannot be checked as ``total`` because the
389checker cannot decide that ``filter (<= x) xs`` will always be smaller
390than ``(x :: xs)``:
392.. code-block:: idris
394 qsort : Ord a => List a -> List a
395 qsort [] = []
396 qsort (x :: xs)
397 = qsort (filter (< x) xs) ++
398 (x :: qsort (filter (>= x) xs))
400The function ``assert_smaller``, defined in the Prelude, is intended to
401address this problem:
403.. code-block:: idris
405 assert_smaller : a -> a -> a
406 assert_smaller x y = y
408It simply evaluates to its second argument, but also asserts to the
409totality checker that ``y`` is structurally smaller than ``x``. This can
410be used to explain the reasoning for totality if the checker cannot work
411it out itself. The above example can now be written as:
413.. code-block:: idris
415 total
416 qsort : Ord a => List a -> List a
417 qsort [] = []
418 qsort (x :: xs)
419 = qsort (assert_smaller (x :: xs) (filter (< x) xs)) ++
420 (x :: qsort (assert_smaller (x :: xs) (filter (>= x) xs)))
422The expression ``assert_smaller (x :: xs) (filter (<= x) xs)`` asserts
423that the result of the filter will always be smaller than the pattern
424``(x :: xs)``.
426In more extreme cases, the function ``assert_total`` marks a
427subexpression as always being total:
429.. code-block:: idris
431 assert_total : a -> a
432 assert_total x = x
434In general, this function should be avoided, but it can be very useful
435when reasoning about primitives or externally defined functions (for
436example from a C library) where totality can be shown by an external
437argument.
440.. [1] Timothy G. Griffin. 1989. A formulae-as-type notion of
441 control. In Proceedings of the 17th ACM SIGPLAN-SIGACT
442 symposium on Principles of programming languages (POPL
443 '90). ACM, New York, NY, USA, 47-58. DOI=10.1145/96709.96714
444 http://doi.acm.org/10.1145/96709.96714