There are two styles of proof in Isabelle: the old "apply" style, where a proof is just a chain of
apply (this method)
apply (that method)
statements, and the new "structured" Isar style. Myself, I find both useful; "apply" style is more concise, and so is handy for uninteresting technical lemmas, while "structured" style is handy for the major theorems.
Sometimes I like to switch from one style to the other, mid-proof. Switching from "apply" style to "structured" style is easy: I just insert
proof -
in my apply-chain. My question is: how can I switch from "structured" style back to "apply" style?
To give a more concrete example: suppose I have five subgoals. I issue some "apply" instructions to despatch the first two subgoals. Then I launch into a structured proof to dispense with the third. I have two subgoals remaining: how do I return to "apply" style for these?
You can continue in "apply" style within a structured proof by using apply_end instead of apply, but this is rarely seen in practice and only during explorative work. In a polished proof, you would just pick the subgoals that merit an Isar proof and finish off all the remaining subgoals in one method call after the qed, as there is no need to deal with the subgoals in any specific order.
Alternatively, you can use defer before you start the structured proof with proof and continue immediately with the other subgoals in "apply" style, i.e., you defer the goals with structured proofs until the end.
And finally, you can of course re-state your goal in the structured proof with fix/assume/show and continue with "apply" style there. But you have to do this for each remaining subgoal separately, so this might be a bit tedious. The default case names goal1, goal2, etc. help a bit with typing, but such proofs are typically hard to maintain (especially as apply_end changes the goal numbering for goal<n>).
Related
I would like to know how to reorder goals in the following situation:
lemma "P=Q"
proof (rule iffI, (*here I would like to swap goal order*), rule ccontr)
oops
I would like a solution that doesn't involve changing the lemma statement. I realise that prefer and defer can be used in apply-style proofs, but I would like to have a method that can be used in the proof (...) part.
Edit:
As Andreas Lochbihler says, writing rule iffI[rotated] works in the above example. However, is it possible to swap the goal order in the following situation without changing the statement of the lemma?
lemma "P==>Q" "Q==>P"
proof ((*here I would like to swap goal order*), rule ccontr)
oops
This example may seem contrived, but I feel that there may be situations where it is inconvenient to change the statement of the lemma, or it is necessary to swap goal order when there is no previous application of a rule such as iffI.
The order of the subgoals is determined by the order of the assumptions of the rule you apply. So it suffices to swap the assumptions of the iffI rule, e.g., using the attribute [rotated] as in
proof(rule iffI[rotated], rule ccontr)
In general, there is no proof method to change the order of the goals. And if you are thinking about using this with more sophisticated proof automation like auto, I'd heavily advise you against doing these kinds of things. Proof scripts with lots of automation in it should work independently of the order of the goals. Otherwise, your proofs will break easily when something in the proof automation setup changes.
However, a few low-level proof tactics allow to use explicit goal addressing (mostly those that end on _tac). For example,
proof(rule iffI, rule_tac [2] ccontr)
applies the ccontr rule to the second subgoal instead of the first.
If Isabelle did not find a proof for a lemma, is it possible to output everything that was done by all the proof methods that were employed in order to arrive at the subgoals, at which they couldn't proceed any further ? This would help me see, at which avenues they got stuck, which then would help me to point them in the right direction.
(And also for completed proofs I would find it interesting to have a complete proof log that shows all the elementary inferences that were performed to proof some lemma.)
This question sounds similar to this one, which I answered a few days ago. Parts of that answer also apply here. Anyway, to answer this question specifically:
Not really. For most basic proof methods (rule et al., intro, fact, cases, induct) it is relatively straightforward what they do and when they fail, it is pretty much always because the rule they tried to apply does not unify with the goals/premises that they are given. (or they don't know which rule to apply in the first place)
You were probably thinking more of more automatic tactics like blast, force, simp, and auto. Most of them (blast, force, fastforce, fast, metis, meson, best, etc.) are ‘all-or-nothing’: They either solve the subgoal or they do nothing at all. It is therefore a bit tricky to find out where they get stuck and usually people use auto for this kind of exploration: You apply auto, look at the remaining subgoals, and think about what facts/parameters you could add in order to break down those more.
The situation with simp is similar, except that it does less than auto. simp is the simplifier, which uses term rewriting, custom rewriting procedures called simprocs, certain solvers (e.g. for linear arithmetic), and a few other convenient things like splitters to get rid of if expressions. auto is basically simp combined with classical reasoning, which makes it a lot more powerful than simp, but also less predictable. (and occasionally, auto does too much and thereby turns a provable goal into an unprovable goal)
There are some tracing tools (e.g. the simplifier trace, which is explained here). I thought there also was a way to trace classical reasoning, but I cannot seem to find it anymore; perhaps I was mistaken. In any case, tracing tools can sometimes help to explain unexpected behaviour, but I don't think they are the kind of thing you want to use here. The better approach is to understand what kinds of things these methods try, and then when simp or auto returns a subgoal, you can look at those and determine what you would have expected simp and auto to do next and why it didn't do that (usually because of some missing fact) and fix it.
I would like to understand how keyword proof works in an Isar proof. I consulted the Isabelle/Isar reference, section 6.3.2 and Programming and Proving in Isabelle/HOL, section 4.1.
To summarise what I have learned, there are three ways of beginning a proof by the keyword proof:
without any argument Isabelle finds a suitable introduction rule to the lemma being proved and applies it in a forward mode
if a hyphen: - is supplied as an argument, then proof does nothing to the goal, avoiding any automatic rule being applied when it would lead to a blind alley
if a specific rule, like rule name, unfold true_def, clarify or induct n is supplied, then it is applied to the goal in a forward mode
Am I right that the third case is like using apply with the argument supplied?
How is the automatic introduction rule in the first case picked by the system?
And, does the above fully describe the usage of proof?
The command proof without a method specification applies the method default. The method default is almost like rule, but if rule fails, then it tries next intro_classes and then unfold_locales. The method rule without being given a list of theorems tries all rules that are declared to the classical reasoner (intro, elim, and dest). If no facts are chained in, only intro rules are considered. Otherwise, all types of rules are tried. All chained-in facts must unify with the rules. dest rules are transformed into elim rules before they are applied.
You can print all declared rules with print_rules. Safe rules (intro!, elim!, ...) are preferred over normal rules (intro, elim, ...) and extra rules (intro?, elim?) come last.
You can also use rule without giving any rules. Then, it behaves like default, but without the fallbacks intro_classes and unfold_locales.
Andreas gave a good description of how proof without a method argument works; I'll just cover some other parts of the question.
First, proof (method) is like apply (method) except for one thing: While apply leaves you in "prove" mode, where you can continue with more apply statements, proof transitions into "state" mode, where you must use a have or show statement to continue proving. Otherwise the effect on the goal state is the same.
I'd also like to point out that case 2 (proof -) is really an instance of case 3, because - is actually an ordinary proof method just like rule name or induct (you can also write apply -, for example). The hyphen - proof method does nothing, except that it will insert chained facts into the current goal, if it is given any chained facts.
I have always thought the definition of both of these were functions that take other functions as arguments. I understand the domain of each is different, but what are their defining characteristics?
Well, let me try to kind of derive their defining characteristics from their different domains ;)
First of all, in their usual context combinators are higher order functions. But as it turns out, context is an important thing to keep in mind when talking about differences of these two terms:
Higher Order Functions
When we think of higher order functions, the first thing usually mentioned is "oh, they (also) take at least one function as an argument" (thinking of fold, etc)... as if they were something special because of that. Which - depending on context - they are.
Typical context: functional programming, haskell, any other (usually typed) language where functions are first class citizens (like when LINQ made C# even more awesome)
Focus: let the caller specify/customize some functionality of this function
Combinators
Combinators are somewhat special functions, primitive ones do not even mind what they are given as arguments (argument type often does not matter at all, so passing functions as arguments is not a problem at all). So can the identity-combinator also be called "higher order function"??? Formally: No, it does not need a function as argument! But hold on... in which context would you ever encounter/use combinators (like I, K, etc) instead of just implementing desired functionality "directly"? Answer: Well, in purely functional context!
This is not a law or something, but I can really not think of a situation where you would see actual combinators in a context where you suddenly pass pointers, hash-tables, etc. to a combinator... again, you can do that, but in such scenarios there should really be a better way than using combinators.
So based on this "weak" law of common sense - that you will work with combinators only in a purely functional context - they inherently are higher order functions. What else would you have available to pass as arguments? ;)
Combining combinators (by application only, of course - if you take it seriously) always gives new combinators that therefore also are higher order functions, again. Primitive combinators usually just represent some basic behaviour or operation (thinking of S, K, I, Y combinators) that you want to apply to something without using abstractions. But of course the definition of combinators does not limit them to that purpose!
Typical context: (untyped) lambda calculus, combinatory logic (surprise)
Focus: (structurally) combine existing combinators/"building blocks" to something new (e.g. using the Y-combinator to "add recursion" to something that is not recursive, yet)
Summary
Yes, as you can see, it might be more of a contextual/philosophical thing or about what you want to express: I would never call the K-combinator (definition: K = \a -> \b -> a) "higher order function" - although it is very likely that you will never see K being called with something else than functions, therefore "making" it a higher order function.
I hope this sort of answered your question - formally they certainly are not the same, but their defining characteristics are pretty similar - personally I think of combinators as functions used as higher order functions in their typical context (which usually is somewhere between special an weird).
EDIT: I have adjusted my answer a little bit since - as it turned out - it was slightly "biased" by personal experience/imression. :) To get an even better idea about correctly distinguishing combinators from HOFs, read the comments below!
EDIT2: Taking a look at HaskellWiki also gives a technical definition for combinators that is pretty far away from HOFs!
What is a combinator??
Is it "a function or definition with no free variables" (as defined on SO)?
Or how about this: according to John Hughes in his well-known paper on Arrows, "a combinator is a function which builds program fragments from program fragments", which is advantageous because "... the programmer using combinators constructs much of the desired program automatically, rather than writing every detail by hand". He goes on to say that map and filter are two common examples of such combinators.
Some combinators which match the first definition:
S
K
Y
others from To Mock a Mockingbird (I may be wrong -- I haven't read this book)
Some combinators which match the second definition:
map
filter
fold/reduce (presumably)
any of >>=, compose, fmap ?????
I'm not interested in the first definition -- those would not help me to write a real program (+1 if you convince me I'm wrong). Please help me understand the second definition. I think map, filter, and reduce are useful: they allow me to program at a higher level -- fewer mistakes, shorter and clearer code. Here are some of my specific questions about combinators:
What are more examples of combinators such as map, filter?
What combinators do programming languages often implement?
How can combinators help me design a better API?
How do I design effective combinators?
What are combinators similar to in a non-functional language (say, Java), or what do these languages use in place of combinators?
Update
Thanks to #C. A. McCann, I now have a somewhat better understanding of combinators. But one question is still a sticking point for me:
What is the difference between a functional program written with, and one written without, heavy use of combinators?
I suspect the answer is that the combinator-heavy version is shorter, clearer, more general, but I would appreciate a more in-depth discussion, if possible.
I'm also looking for more examples and explanations of complex combinators (i.e. more complex than fold) in common programming languages.
I'm not interested in the first definition -- those would not help me to write a real program (+1 if you convince me I'm wrong). Please help me understand the second definition. I think map, filter, and reduce are useful: they allow me to program at a higher level -- fewer mistakes, shorter and clearer code.
The two definitions are basically the same thing. The first is based on the formal definition and the examples you give are primitive combinators--the smallest building blocks possible. They can help you to write a real program insofar as, with them, you can build more sophisticated combinators. Think of combinators like S and K as the machine language of a hypothetical "combinatory computer". Actual computers don't work that way, of course, so in practice you'll usually have higher-level operations implemented behind the scenes in other ways, but the conceptual foundation is still a useful tool for understanding the meaning of those higher-level operations.
The second definition you give is more informal and about using more sophisticated combinators, in the form of higher-order functions that combine other functions in various ways. Note that if the basic building blocks are the primitive combinators above, everything built from them is a higher-order function and a combinator as well. In a language where other primitives exist, however, you have a distinction between things that are or are not functions, in which case a combinator is typically defined as a function that manipulates other functions in a general way, rather than operating on any non-function things directly.
What are more examples of combinators such as map, filter?
Far too many to list! Both of those transform a function that describes behavior on a single value into a function that describes behavior on an entire collection. You can also have functions that transform only other functions, such as composing them end-to-end, or splitting and recombining arguments. You can have combinators that turn single-step operations into recursive operations that produce or consume collections. Or all kinds of other things, really.
What combinators do programming languages often implement?
That's going to vary quite a bit. There're relatively few completely generic combinators--mostly the primitive ones mentioned above--so in most cases combinators will have some awareness of any data structures being used (even if those data structures are built out of other combinators anyway), in which case there are typically a handful of "fully generic" combinators and then whatever various specialized forms someone decided to provide. There are a ridiculous number of cases where (suitably generalized versions of) map, fold, and unfold are enough to do almost everything you might want.
How can combinators help me design a better API?
Exactly as you said, by thinking in terms of high-level operations, and the way those interact, instead of low-level details.
Think about the popularity of "for each"-style loops over collections, which let you abstract over the details of enumerating a collection. These are just map/fold operations in most cases, and by making that a combinator (rather than built-in syntax) you can do things such as take two existing loops and directly combine them in multiple ways--nest one inside the other, do one after the other, and so on--by just applying a combinator, rather than juggling a whole bunch of code around.
How do I design effective combinators?
First, think about what operations make sense on whatever data your program uses. Then think about how those operations can be meaningfully combined in generic ways, as well as how operations can be broken down into smaller pieces that are connected back together. The main thing is to work with transformations and operations, not direct actions. When you have a function that just does some complicated bit of functionality in an opaque way and only spits out some sort of pre-digested result, there's not much you can do with that. Leave the final results to the code that uses the combinators--you want things that take you from point A to point B, not things that expect to be the beginning or end of a process.
What are combinators similar to in a non-functional language (say, Java), or what do these languages use in place of combinators?
Ahahahaha. Funny you should ask, because objects are really higher-order thingies in the first place--they have some data, but they also carry around a bunch of operations, and quite a lot of what constitutes good OOP design boils down to "objects should usually act like combinators, not data structures".
So probably the best answer here is that instead of combinator-like things, they use classes with lots of getter and setter methods or public fields, and logic that mostly consists of doing some opaque, predefined action.