I have a rather large term foo. When I type
value "foo"
then Isabelle evaluates foo to a value, say foo_value. I would now like to prove the following lemma.
lemma "foo = foo_value"
What proof method should I use? I tried try, but that timed out. I guess I could proceed manually by unfolding the various definitions that occur in foo, but surely I should be able to tap into whatever mechanism the value command is using, right?
There are three proof methods that correspond to the different evaluation mechanisms of value:
eval uses the code generator; it corresponds to value [code]. The proof succeeds if the generated ML code evaluates to True.
normalization compiles the statement to a symbolic normalisation engine in ML. It mimicks value [nbe].
code_simp uses Isabelle's simplifier as an evaluator. It corresponds to value [simp].
The tutorial on code generation describes these proof methods in more detail. eval and normalization act like oracles, i.e., they bypass Isabelle's kernel whereas every evaluation step of code_simp goes through the kernel. Usually, eval is faster than normalization and normalization is faster than code_simp.
I am not sure whether it works in all cases, but you could try:
lemma "foo = foo_value"
by eval
In many cases, by simp should also work and I guess eval is kind of an oracle (in the sense that it is not fully verified by the kernel; please somebody correct me if I am wrong).
Related
I am trying to create my first Isabelle/HOL theory for the algorithm, that computer max integer from the two integers. My intention is to formalize algorithms, this is the initial attempt. That is why I am trying to make my Isabelle code as similar to usual s. c. "industrial programming langauges" or the algol-style code as possible. So - no much use of the default primrec and recursive definitions (with matching and branching) that Isabelle prefers. I came up with the theory:
theory Max_Of_Two_Integers
imports Main
begin
function two_integer_max :: "nat ⇒ nat ⇒ nat"
where
"two_integer_max first second =
(if (first > second) then
return first
else
return second)"
end
But there is error message at function keyword:
Variable "return" occurs on right hand side only:
⋀first second return.
two_integer_max first second =
(if second < first then return first else return second)
I am closely following the pattern of very elaborate algorithm https://isabelle.in.tum.de/library/HOL/HOL-Imperative_HOL/Imperative_Quicksort.html and it used return as expected in usual programming.
So - what is wrong with my return command and how to correct it?
Besides, for this theory to be complete I have to prove termination (theorems about the nature of the function are not mandatory but I will add them).
Isabelle tutorial mainly uses primrec with recursive definition of functions and with pattern matching. I opted for function with the flexibility that comes with it (my guess).
Besides Tutorial, the blog post https://www.joachim-breitner.de/blog/732-Isabelle_functions__Always_total%2C_sometimes_undefined is fantastic guide to functions for Isabelle - but it is strange - it does not provide the example with return keyword, although - as one can see in Imperative_Quicksort example, function and return are valid constructions.
The return is specific to writing imperative algorithms in Imperative HOL (which is what that Quicksort implementation is using). The same goes for the do notation. The reason why you get that error message is that you have not imported Imperative HOL.
Imperative HOL is essentially a library that allows you to write imperative programs that modify a heap. That's why the return type of quicksort in that example is not a list or an array, but a unit Heap. (But even if you had, you would still get an error message, since you are returning a nat and not a Heap).
If you're just starting out with Isabelle, I strongly recommend not to do anything with Imperative HOL for now. Rather, you should start with ‘normal’ Isabelle, which uses a functional style to define things. A good introduction into Isabelle is the first half of the Concrete Semantics book, which is available free online.
When I use value to find out some value of a function that returns natural numbers, I always obtain the answer in the form of iterated Successor functions of 0, i.e., Suc(Suc( ... 0 )) which can be hard to read sometimes.
Is there a way to output directly the number that Isabelle returns?
This is something I wanted to fix some time ago but apparently I forgot. Carcigenate's guess is incorrect; this is indeed the fully evaluated result. The problem is just that natural numbers are printed in this unwieldy way.
You can do the following things:
The easiest way is to convert the number to an integer, i.e. instead of value "foo x y z" (where foo x y z is the expression of type nat that you want to eavluate) you write value "int (foo x y z)".
You can add ~~/src/HOL/Library/Code_Target_Numeral to your imports. This makes Isabelle's code generator use arbitrary-precision integers of the target language (in case of value, that's ML and its GMP-based integers) instead of the inefficient successor notation. As a side effect, this also prints natural numbers in a nicer way.
You can add the following code to your theory, which changes the way the value command displays natural numbers:
Code:
lemma Suc_numeral_bit0: "Suc (numeral (Num.Bit0 n)) = numeral (Num.Bit1 n)"
by (subst Suc_numeral) simp
lemma Suc_numeral_bit1: "Suc (numeral (Num.Bit1 n)) = numeral (Num.Bit0 (n + Num.One))"
by (subst Suc_numeral) simp
lemmas [code_post] =
One_nat_def [symmetric] Suc_numeral_bit0 Suc_numeral_bit1 Num.Suc_1 Num.arith_simps
Note that the value command is a diagnostic command only. It is mainly used for quick plausibility tests and debugging of code generation setups, and getting it to work can sometimes be tricky.
By default, value relies on the code generator, i.e. Isabelle needs to know how to generate executable code for the expression, and if it cannot do that, value will probably fail. (It can sometimes also fall back to some other strategies, normalisation by evaluation or evaluation by simplification, but those usually don't provide useful output)
I'm just telling you this so that you know what to expect from the value command and don't get the impression that this is some integral part of Isabelle that people use all the time.
I have seen a lot of documentation about Isabelle's syntax and proof strategies. However, little have I found about its foundations. I have a few questions that I would be very grateful if someone could take the time to answer:
Why doesn't Isabelle/HOL admit functions that do not terminate? Many other languages such as Haskell do admit non-terminating functions.
What symbols are part of Isabelle's meta-language? I read that there are symbols in the meta-language for Universal Quantification (/\) and for implication (==>). However, these symbols have their counterpart in the object-level language (∀ and -->). I understand that --> is an object-level function of type bool => bool => bool. However, how are ∀ and ∃ defined? Are they object-level Boolean functions? If so, they are not computable (considering infinite domains). I noticed that I am able to write Boolean functions in therms of ∀ and ∃, but they are not computable. So what are ∀ and ∃? Are they part of the object-level? If so, how are they defined?
Are Isabelle theorems just Boolean expressions? Then Booleans are part of the meta-language?
As far as I know, Isabelle is a strict programming language. How can I use infinite objects? Let's say, infinite lists. Is it possible in Isabelle/HOL?
Sorry if these questions are very basic. I do not seem to find a good tutorial on Isabelle's meta-theory. I would love if someone could recommend me a good tutorial on these topics.
Thank you very much.
You can define non-terminating (i.e. partial) functions in Isabelle (cf. Function package manual (section 8)). However, partial functions are more difficult to reason about, because whenever you want to use its definition equations (the psimps rules, which replace the simps rules of a normal function), you have to show that the function terminates on that particular input first.
In general, things like non-definedness and non-termination are always problematic in a logic – consider, for instance, the function ‘definition’ f x = f x + 1. If we were to take this as an equation on ℤ (integers), we could subtract f x from both sides and get 0 = 1. In Haskell, this problem is ‘solved’ by saying that this is not an equation on ℤ, but rather on ℤ ∪ {⊥} (the integers plus bottom) and the non-terminating function f evaluates to ⊥, and ‘⊥ + 1 = ⊥’, so everything works out fine.
However, if every single expression in your logic could potentially evaluate to ⊥ instead of a ‘proper‘ value, reasoning in this logic will become very tedious. This is why Isabelle/HOL chooses to restrict itself to total functions; things like partiality have to be emulated with things like undefined (which is an arbitrary value that you know nothing about) or option types.
I'm not an expert on Isabelle/Pure (the meta logic), but the most important symbols are definitely
⋀ (the universal meta quantifier)
⟹ (meta implication)
≡ (meta equality)
&&& (meta conjunction, defined in terms of ⟹)
Pure.term, Pure.prop, Pure.type, Pure.dummy_pattern, Pure.sort_constraint, which fulfil certain internal functions that I don't know much about.
You can find some information on this in the Isabelle/Isar Reference Manual in section 2.1, and probably more elsewhere in the manual.
Everything else (that includes ∀ and ∃, which indeed operate on boolean expressions) is defined in the object logic (HOL, usually). You can find the definitions, of rather the axiomatisations, in ~~/src/HOL/HOL.thy (where ~~ denotes the Isabelle root directory):
All_def: "All P ≡ (P = (λx. True))"
Ex_def: "Ex P ≡ ∀Q. (∀x. P x ⟶ Q) ⟶ Q"
Also note that many, if not most Isabelle functions are typically not computable. Isabelle is not a programming language, although it does have a code generator that allows exporting Isabelle functions as code to programming languages as long as you can give code equations for all the functions involved.
3)
Isabelle theorems are a complex datatype (cf. ~~/src/Pure/thm.ML) containing a lot of information, but the most important part, of course, is the proposition. A proposition is something from Isabelle/Pure, which in fact only has propositions and functions. (and itself and dummy, but you can ignore those).
Propositions are not booleans – in fact, there isn't even a way to state that a proposition does not hold in Isabelle/Pure.
HOL then defines (or rather axiomatises) booleans and also axiomatises a coercion from booleans to propositions: Trueprop :: bool ⇒ prop
Isabelle is not a programming language, and apart from that, totality does not mean you have to restrict yourself to finite structures. Even in a total programming language, you can have infinite lists. (cf. Idris's codata)
Isabelle is a theorem prover, and logically, infinite objects can be treated by axiomatising them and then reasoning about them using the axioms and rules that you have.
For instance, HOL assumes the existence of an infinite type and defines the natural numbers on that. That already gives you access to functions nat ⇒ 'a, which are essentially infinite lists.
You can also define infinite lists and other infinite data structures as codatatypes with the (co-)datatype package, which is based on bounded natural functors.
Let me add some points to two of your questions.
1) Why doesn't Isabelle/HOL admit functions that do not terminate? Many other languages such as Haskell do admit non-terminating functions.
In short: Isabelle/HOL does not require termination, but totality (i.e., there is a specific result for each input to the function) of functions. Totality does not mean that a function is actually terminating when transcribed to a (functional) programming language or even that it is computable at all.
Therefore, talking about termination is somewhat misleading, even though it is encouraged by the fact that Isabelle/HOL's function package uses the keyword termination for proving some property P about which I will have to say a little more below.
On the one hand the term "termination" might sound more intuitive to a wider audience. On the other hand, a more precise description of P would be well-foundedness of the function's call graph.
Don't get me wrong, termination is not really a bad name for the property P, it is even justified by the fact that many techniques that are implemented in the function package are very close to termination techniques from term rewriting or functional programming (like the size-change principle, dependency pairs, lexicographic orders, etc.).
I'm just saying that it can be misleading. The answer to why that is the case also touches on question 4 of the OP.
4) As far as I know Isabelle is a strict programming language. How can I use infinite objects? Let's say, infinite lists. Is it possible in Isabelle/HOL?
Isabelle/HOL is not a programming language and it specifically does not have any evaluation strategy (we could alternatively say: it has any evaluation strategy you like).
And here is why the word termination is misleading (drum roll): if there is no evaluation strategy and we have termination of a function f, people might expect f to terminate independent of the used strategy. But this is not the case. A termination proof of a function rather ensures that f is well-defined. Even if f is computable a proof of P merely ensures that there is an evaluation strategy for which f terminates.
(As an aside: what I call "strategy" here, is typically influenced by so called cong-rules (i.e., congruence rules) in Isabelle/HOL.)
As an example, it is trivial to prove that the function (see Section 10.1 Congruence rules and evaluation order in the documentation of the function package):
fun f' :: "nat ⇒ bool"
where
"f' n ⟷ f' (n - 1) ∨ n = 0"
terminates (in the sense defined by termination) after adding the cong-rule:
lemma [fundef_cong]:
"Q = Q' ⟹ (¬ Q' ⟹ P = P') ⟹ (P ∨ Q) = (P' ∨ Q')"
by auto
Which essentially states that logical-or should be "evaluated" from right to left. However, if you write the same function e.g. in OCaml it causes a stack overflow ...
EDIT: this answer is not really correct, check out Lars' comment below.
Unfortunately I don't have enough reputation to post this as a comment, so here is my go at an answer (please bear in mind I am no expert in Isabelle, but I also had similar questions once):
1) The idea is to prove statements about the defined functions. I am not sure how familiar you are with Computability Theory, but think about the Halting Problem and the fact most undeciability problems stem from it (such as Acceptance Problem). Imagine defining a function which you can't prove it terminates. How could you then still prove it returns the number 42 when given input "ABC" and it doesn't go in an infinite loop?
If instead you limit yourself to terminating functions, you can prove much more about them, essentially making a trade-off (or at least this is how I see it).
These ideas stem from Constructivism and Intuitionism and I recommend you check out Robert Harper's very interesting lecture series: https://www.youtube.com/watch?v=9SnefrwBIDc&list=PLGCr8P_YncjXRzdGq2SjKv5F2J8HUFeqN on Type Theory
You should check out especially the part about the absence of the Law of Excluded middle: http://youtu.be/3JHTb6b1to8?t=15m34s
2) See Manuel's answer.
3,4) Again see Manuel's answer keeping in mind Intuitionistic logic: "the fundamental entity is not the boolean, but rather the proof that something is true".
For me it took a long time to get adjusted to this way of thinking and I'm still not sure I understand it. I think the key though is to understand it is a more-or-less completely different way of thinking.
When I try to write
fun foo :: "nat ⇒ nat"
where "foo = Suc"
Isabelle complains that "Function has no arguments". Why is this? What's wrong with a fun having no arguments? I know that I can change fun to abbreviation or definition and all is fine. But it seems a shame to spoil the uniformity of my .thy file, in which every other definition is declared with fun.
Alex Krauss, the author of the present generation of fun and function in Isabelle/HOL had particular opinions about that, and probably also good formal reasons to say that a "function" really needs to have arguments. In SML you actually have a similar situation: "constants" without arguments are defined via val not fun.
In the rare situations, where zero-argument functions are needed in Isabelle/HOL, it is sufficiently easy to use definition [simp] "c = t to get mostly the same result, apart from the name of the key theorems produced internally: c_def versus c.simps.
I think the main inconvenience and occasional pitfal of function in this respect is its exposure of the auxiliary c_def that is not meant to be used in applications: it unfolds the internal construction behind the function specification, not its main characterizing equation.
Since no other answer seems to be on its way, let me repeat and extend on my previous comment. In Isabelle/HOL there are three ways of defining functions:
definition for non-recursive functions (which could just be seen as constants that serve as abbreviations for longer statements).
primrec for primitive recursive functions (in the sense that in every recursive call there is a fixed argument where a datatype constructor is removed).
fun for general recursive functions.
Both, primrec and fun expect at least one argument. For the former it is automatically checked that one of its arguments corresponds to the syntactic pattern of primitive recursion on datatypes, while for the latter the task of proving "termination" (or rather the well-foundedness of the call graph) will be delegated to the user in hard cases.
Anyway, it would of course be possible to relay primrec and fun to definition for easy cases without arguments, but at least to me this rather seems to obfuscate things for the user instead of clearing them up.
I have a term in mind, say "foo 1 2 a b", and I'd like to know if Isabelle can simplify it for me. I'd like to write something like
simplify "foo 1 2 a b"
and have the simplified term printed in the output buffer. Is this possible?
My current 'workaround' is:
lemma "foo 1 2 a b = blah"
apply simp
which works fine but looks a bit hacky.
What doesn't work (in my case) is:
value "foo 1 2 a b"
because a and b are unbound variables, and because my foo involves infinite sets and other fancy stuff, which the code generator chokes on.
There is no built-in feature AFAIK, but there are several ways to achieve this. You have already discovered one of them, namely state the term as a lemma and then invoke the simplifier. The drawback is that this cannot be used in all contexts, for example, not inside an apply proof script.
Alternatively, you can invoke the simplifier via the attribute [simplified]. This works in all contexts via the thm command and produces the output in the output buffer. First, the term must be injected into a theorem, then you can apply simplify to the theorem and display the result with thm. Here is the preparatory stuff that can go into your theory of miscellaneous stuff.
definition simp :: "'a ⇒ bool" where "simp _ = True"
notation (output) simp ("_")
lemma simp: "simp x" by(simp add: simp_def)
Then, you can write
thm simp[of "foo 1 2 a b", simplified]
and see the simplified term in the output window.
The evaluation mechanism is probably not what you want, because evaluation uses a different set of rewrite rules (namely the code equations) than the simplifier normally uses (the simpset). Therefore, this is likely to evaluate to a different term than by applying the simplifier. To see the difference, apply code_simp instead of simp in your approach with lemma "foo 1 2 a b = blah". The proof method code_simp uses the code equations just like value [simp] used to.
When using the value command, the evaluation of the argument is conducted by registered evaluators (see the Reference Manual of Isabelle2013-2).
It used to be possible to explicitly choose an evaluator in previous versions of Isabelle (e.g., Isabelle2013-2) by giving an extra argument to the value command. E.g.,
value [simp] "foo 1 2 a b"
It seems that in Isabelle2014 this parameter was dropped and according to the Reference Manual of Isabelle2014, the strategy is now fixed to first use ML code generation and in case this fails, normalization by evaluation.
From the NEWS file in the development version (e82c72f3b227) of Isabelle it seems as if this parameter will be enabled again in the upcoming Isabelle release.
UPDATE: As Andreas pointed out, value [simp] does not use the same set of simplification rules as apply simp. So even if available, the solution I described above will most likely not yield the result you want.