Occasionally there is a lemma, where try0 crashes Isabelle/JEdit. Only kill -9 on the process id works then.
When I then call try in the same situation, Isabelle/JEdit crashes again.
Is it possible to invoke in this situation everything from try without running try0 (or otherwise omit all the proves try0 would call)?
(I did some google searches but could not find anything relevant.)
From isar-ref.pdf, page 267, it gives the following notes on try0 and try:
try0 attempts to prove a subgoal using a combination of standard proof methods (auto, simp, blast, etc.). Additional facts supplied via simp:, intro:, elim:, and dest: are passed to the appropriate proof methods.
try attempts to prove or disprove a subgoal using a combination of provers and disprovers (solve_direct, quickcheck, try0, sledgehammer, nitpick).
So if the description is complete in the Isar Reference manual, then try is try0 plus two provers, solve_direct and sledgehammer, and two counterexample finders, quickcheck and nitpick.
To not run the provers of try0, as far as I know, you would have to run solve_direct and sledgehammer individually.
If try0 is hanging the PIDE, then it's mostly likely going into a bad loop of some kind, where enabling Auto Methods in Plugin Options / Isabelle can also cause these loops, since it does something similar to try0.
There are two kind of loops that I've experienced, where I can only remember the 2nd one listed next as hanging the PIDE:
simp rule loops caused by simp or methods that call simp, such as auto, force, fastforce, slowsimp, and bestsimp. To stop an infinite loop, I just have to delete the command.
blast loops caused by auto calling blast. For these kind, it either happens immediately, or after I've let the command run for 5 seconds or so.
You can try to narrow down which automatic proof method is causing try0 and try to hang the PIDE.
You can get a list of the try0 methods used like this (I don't know if the list of provers it shows is complete):
lemma "False"
try0
(*Trying "simp", "auto", "fast", "fastforce", "force", "blast", "metis",
"linarith", and "presburger"...*)
oops
From that list, if apply(simp) is looping, you know there's a simp rule loop (or simp rules that take a long time to complete), which would make auto, fastforce and force loop, since they call simp.
If auto is looping, then it could be simp or blast, since auto can call each of those, so you can try apply(blast) to see if blast is the problem.
With Isabelle2013-2, for me, the hanging of the PIDE has been because of auto, because it calls blast. Supposedly, that has been fixed for the next release.
I put this as a second answer, since it's different enough.
I was browsing through the repository changes, and that led me to the following Isabelle2013-2 ML files related to try and try0 (but don't get locked in completely, because I list other files to show things are changing):
src/Tools/try.ML
src/HOL/Tools/try0.ML
src/HOL/Tools/Sledgehammer/sledgehammer_try0.ML
The file try.ML gives this line of source:
val get_tools : theory -> tool list
I suppose, using ML, a person could get a list of the tools used by try, but if so, then it would probably correspond with the description given in the other answer from isar-ref.pdf.
Basically, there is try which calls other tools, sledgehammer which does its own special thing, where the use of try0 is part of its arsenal, solve_direct, and then fully automatic proof methods used by try0, which are listed in try0.ML.
The majority of automatic proof methods which are listed in try0.ML are referenced in isar-ref.pdf sections
9.3 The Simplifier [187]
9.4.4 Fully automated methods [211],
12.7 Model Elimination and Resolution [263], and
12.8 Algebraic reasoning via Gröbner bases [263].
Now for some changes that are coming down the pipe, taken from the latest files or changesets:
src/HOL/Tools/try0.ML
Changeset: got rid of 'try0' step that is now redundant
Changeset: implemented new 'try0_isar' semantics
From the latest repository try0.ML, it gives the list of proof methods that try0 will try. If you look at the Isabelle2013-2 try0.ML above, you'll see that the list given in that file is the same list of methods shown in the other answer when trying to prove False.
Here, there are extra automatic proof methods that aren't currently being tried with Isabelle2013-2.
val full_attrs = (SOME "simp", SOME "intro", SOME "elim", SOME "dest");
val clas_attrs = (NONE, SOME "intro", SOME "elim", SOME "dest");
val simp_attrs = (SOME "add", NONE, NONE, NONE);
val metis_attrs = (SOME "", SOME "", SOME "", SOME "");
val no_attrs = (NONE, NONE, NONE, NONE);
(* name * ((all_goals, run_if_auto_try), (simp, intro, elim, dest) *)
val named_methods =
[("simp", ((false, true), simp_attrs)),
("auto", ((true, true), full_attrs)),
("blast", ((false, true), clas_attrs)),
("metis", ((false, true), metis_attrs)),
("linarith", ((false, true), no_attrs)),
("presburger", ((false, true), no_attrs)),
("algebra", ((false, true), no_attrs)),
("fast", ((false, false), clas_attrs)),
("fastforce", ((false, false), full_attrs)),
("force", ((false, false), full_attrs)),
("meson", ((false, false), metis_attrs))]
Related
Can I somehow refer to the name of a lemma (or theorem or corollary) inside Isabelle text?
For example:
theory Scratch
imports Main
begin
lemma lemma_name: "stuff = stuff" by simp
text‹As we have proven in fact #{thm lemma_name}, stuff is stuff.›
end
When compiling this to pdf, I see
As we have proven in fact stuff=stuff, stuff is stuff.
I would like to see
As we have proven in fact lemma_name, stuff is stuff.
Is there some document antiqotation which just prints the name of a lemma?
I could just type the lemma name verbatim, but this neither gives me control-click in the IDE nor does it make sure the text still refers to a true fact, even if I rename lemmas.
The output of antiquotations can be changed by options explained in 4.2.1 and 4.2.2 of the Isabelle/Isar Reference Manual. One (rather hidden) option [source=true] sets the output to print what you have entered as argument to the antiquotation instead of its output.
text ‹As we have proven in fact #{thm [source] lemma_name}, stuff is stuff.
...will thus result in the document output:
As we have proven in fact lemma_name, stuff is stuff.
The checking of the validity of the reference will still take place during document preparation.
Background
While playing around with dialyzer, typespecs and currying, I was able to create an example of a false positive in dialyzer.
For the purposes of this MWE, I am using diallyxir (versions included) because it makes my life easier. The author of dialyxir confirmed this was not a problem on their side, so that possibility is excluded for now.
Environment
$ elixir -v
Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
Elixir 1.13.2 (compiled with Erlang/OTP 24)
Which version of Dialyxir are you using? (cat mix.lock | grep dialyxir):
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
Current behavior
Given the following code sample:
defmodule PracticingCurrying do
#spec greater_than(integer()) :: (integer() -> String.t())
def greater_than(min) do
fn number -> number > min end
end
end
Which clearly has a wrong typing, I get a success message:
$ mix dialyzer
Compiling 1 file (.ex)
Generated grokking_fp app
Finding suitable PLTs
Checking PLT...
[:compiler, :currying, :elixir, :gradient, :gradualizer, :kernel, :logger, :stdlib, :syntax_tools]
Looking up modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding applications for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding modules for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Checking 518 modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Adding 44 modules to dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
done in 0m24.18s
No :ignore_warnings opt specified in mix.exs and default does not exist.
Starting Dialyzer
[
check_plt: false,
init_plt: '/home/user/Workplace/fl4m3/grokking_fp/_build/dev/dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt',
files: ['/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.ImmutableValues.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.PracticingCurrying.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.TipCalculator.beam'],
warnings: [:unknown]
]
Total errors: 0, Skipped: 0, Unnecessary Skips: 0
done in 0m1.02s
done (passed successfully)
Expected behavior
I expected dialyzer to tell me the correct spec is #spec greater_than(integer()) :: (integer() -> bool()).
As a side note (and comparison, if you will) gradient does pick up the error.
I know that comparing these tools is like comparing oranges and apples, but I think it is still worth mentioning.
Questions
Is dialyzer not intended to catch this type of error?
If it should catch the error, what can possibly be failing? (is it my example that is incorrect, or something inside dialyzer?)
I personally find it hard to believe this could be a bug in Dialyzer, the tool has been used rather extensively by a lot of people for me to be the first to discover this error. However, I cannot explain what is happening.
Help is appreciated.
Dialyzer is pretty optimistic in its analysis and ignores some categories of errors.
This article provides some advanced explanations about its approach and limitations.
In the particular case of anonymous functions, dialyzer seems to perform a very minimal check
when they are being declared: it will ignore both the types of its arguments and return type, e.g.
the following doesn't lead any error even if is clearly wrong:
# no error
#spec add(integer()) :: (String.t() -> String.t())
def add(x) do
fn y -> x + y end
end
It will however point out a mismatch in arity, e.g.
# invalid_contract
# The #spec for the function does not match the success typing of the function.
#spec add2(integer()) :: (integer(), integer() -> integer())
def add2(x) do
fn y -> x + y end
end
Dialyzer might be able to detect a type conflict when trying to use the anonymous function,
but this isn't guaranteed (see article above), and the error message might not be helpful:
# Function main/0 has no local return.
def main do
positive? = greater_than(0)
positive?.(2)
end
We don't know what is the problem exactly, not even the line causing the error. But at least we know there is one and can debug it.
In the following example, the error is a bit more informative (using :lists.map/2 instead of Enum.map/2 because
dialyzer doesn't understand the enumerable protocol):
# Function main2/0 has no local return.
def main2 do
positive? = greater_than(0)
# The function call will not succeed.
# :lists.map(_positive? :: (integer() -> none()), [-2 | 0 | 1, ...])
# will never return since the success typing arguments are
# ((_ -> any()), [any()])
:lists.map(positive?, [1, 0, -2])
end
This tells us that dialyzer inferred the return type of greater_than/1 to be (integer() -> none()).
none is described in the article above as:
This is a special type that means that no term or type is valid.
Usually, when Dialyzer boils down the possible return values of a function to none(), it means the function should crash.
It is synonymous with "this stuff won't work."
So dialyzer knows that this function cannot be called successfully, but doesn't consider it to be a type clash until actually called, so it will allow the declaration (in the same way you can perfectly create a function that just raises).
Disclaimer: I couldn't find an official explanation regarding how dialyzer handles anonymous
functions in detail, so the explanations above are based on my observations and interpretation
By accident, I recently came across a latent coding error in one of my functions, dealing with a when statement. A reduced paraphrase might look like:
(defparameter a 0)
(when (or (= a 0)
(= a 1)
(* a a)))
The problem is a misplaced parenthesis, so it actually is
(when (or (= a 0)
(= a 1)
(* a a)))
In a situation like this, wouldn't it be useful for the compiler to generate either a style warning or note? It seems to me that the meaning of a when statement normally implies a condition and a body, even though the body is strictly optional. Of course, a print pretty would have caught this in the editor, but I had copied it from elsewhere. Is there a reason that SBCL does not check for these kinds of mistakes?
a print pretty would have caught this in the editor
To discuss the options, I know about:
trivial-formatter will format the source code.
(trivial-formatter:fmt :your-system :supersede)
cl-indentify indents the source code. Has a command line utility. I tried it once and it was not bad, but different than Emacs' indentation, thus annoying for me.
$ cl-indentify bar.lisp
It links to lispindent but I was less happy with its result.
However, the best would be to not only format the code and re-read it ourselves, but to
run checks against a set of rules to warn against code smells
This is what proposes the lisp-critic. It can critique a function or a file. However:
(edit) it doesn't really have a Slime integration, we have to either critique a function or a whole file.
if you feel adventurous, see an utility of mine here. It could be an easier way to test snippets that you enter at the REPL.
it hasn't the rule about when without a body (we can easily add it)
And it would be best that the run failed with an error status code if it found a code smell. Again, a little project of mine in beta tries to do that, see here. It doesn't have much rules now, but I just pushed a check for this. You can call the script:
$colisper.sh tests/playground.lisp
it shows an error (but doesn't write it in-place by default):
|;; when with no body
|(when (or (= a 0)
| (= a 1)
!| (* a a))
!| (error "colisper found a 'when' with a missing body. (we should error the script without this rewrite!)"))
and returns with an exit code, so we can use it has a git hook or on a CI pipeline.
The problem is that if a human writes (when x) (or whatever that expands into, perhaps (if x (progn) nil)) this is probably a mistake, but when a program writes it it may well not be: it may be just some edge case that the program hasn't been smart enough to optimize completely away. And a huge amount of code that the compiler processes is written by programs, not humans.
In Isabelle/HOL, how do I find where a given type was instantiated for a given class? For the sake of this post for example, where real was instantiated as a conditionally_complete_linorder. To justify the question: I might want to know this for inspiration for a similar instantiation, for showing it to someone(s), for Isabelle/HOL practice reading, for curiosity, and so on. My process at the moment:
First, check it actually is: type instantiation real :: conditionally_complete_linorder begin end and see if I get the error message "No parameters and no pending instance proof obligations in instantiation."
Next, ideally before where I'd need to know how i.e. whether it was direct, or implicit via classes C_1[, C_2, C_3, etc]. Then, I would need to find where those instantiations are, either an explicit instantiation real :: conditionally_complete_linorder or the implicit ones for the C_i (same process for either case ofc). I don't know how to find out how, so I have to check for an explicit instantiation, then all possible implicit instantiations.
For explicit, I can do a grep -Ern ~/.local/src/Isabelle2019 -e 'instantiation real :: conditionally_complete_linorder' (and hope the whitespace isn't weird, or do a more robust search :)). Repeat for AFP location. Alternatively, to stay within the jEdit window:
I can find where the class itself was defined by typing term "x::'a::conditionally_complete_linorder" then Ctrl-clicking the class name, and then check if real is directly instantiated in that file with Ctrl-f.
I could then check if it's instantiated where the type real is defined by typing term "x::real" and Ctrl-clicking real, then Ctrl-f for conditionally_complete_linorder in that file.
(If it is in either place it'll be whichever is further down in the import hierarchy, but I find just going through those two steps simpler.) However, if neither two places turn it up then either, for whatever reason, it is explicitly instantiated somewhere else or is implicitly instantiated. For that reason grep is more robust.
If explicit turns nothing up then I check implicit. Looking at the class_deps graph I can see that conditionally_complete_linorder can follow from either complete_linorder or linear_continuum. I can then continue the search by seeing if real is instantiated as either of them (disregarding any I happen to know real can't be instantiated as). I can also check to see if it's instantiated as both conditioanlly_complete_lattice and linorder, which is what I can see conditionally_complete_linorder is a simple (no additional assumptions) combination of*. Repeat for all of these classes recursively until the instantiations are found. In this case, I can see that linear_continuum_topology implies linear_continuum, so kill two birds with one stone with grep -Ern ~/.local/src/Isabelle2019 -e "instantiation.*real" | grep continuum and find /path/to/.local/src/Isabelle2019/src/HOL/Real.thy:897:instantiation real :: linear_continuum.
This process is quite tedious. Less but still quite tedious** would be to get the class_deps graph up and Ctrl-f for "instantiation real" in Real.thy and look for instantiations of: the original class, the superclasses of it, or the classes which imply it. Then in the files each those classes are defined search for "instantiation real". Do this recursively till done. In this case I would have found what I needed in Real.thy.
Is there an easier way? Hope I just missed something obvious.
* I can't Ctrl-click in Conditionally_Complete_Lattices.thy to jump to linorder directly, I guess because of something to do with it being pre-built, so I have to do the term "x::'a::linorder" thing again.
** And also less robust, as it is minus grep-ing which can turn up weirder instantiation locations, then again I'm not sure if this ever comes up in practice.
Thanks
You can import the theory in the code listing below and then use the command find_instantiations. I will leave the code without further explanation, but please feel free to ask further questions in the comments if you need further details or suspect that something is not quite right.
section ‹Auxiliary commands›
theory aux_cmd
imports Complex_Main
keywords "find_instantiations" :: thy_decl
begin
subsection ‹Commands›
ML ‹
fun find_instantiations ctxt c =
let
val {classes, ...} = ctxt |> Proof_Context.tsig_of |> Type.rep_tsig;
val algebra = classes |> #2
val arities = algebra |> Sorts.arities_of;
in
Symtab.lookup arities c
|> the
|> map #1
|> Sorts.minimize_sort algebra
end
fun find_instantiations_cmd tc st =
let
val ctxt = Toplevel.context_of st;
val _ = tc
|> Syntax.parse_typ ctxt
|> dest_Type
|> fst
|> find_instantiations ctxt
|> map Pretty.str
|> Pretty.writeln_chunks
in () end
val q = Outer_Syntax.command
\<^command_keyword>‹find_instantiations›
"find all instantiations of a given type constructor"
(Parse.type_const >> (fn tc => Toplevel.keep (find_instantiations_cmd tc)));
›
subsection ‹Examples›
find_instantiations filter
find_instantiations nat
find_instantiations real
end
Remarks
I would be happy to provide amendments if you find any problems with it, but do expect a reasonable delay in further replies.
The command finds both explicit and implicit instantiations, i.e. it also finds the ones that were achieved by means other than the use of the commands instance or instantiation, e.g. inside an ML function.
Unfortunately, the command does not give you the location of the file where the instantiation was performed - this is something that would be more difficult to achieve, especially, given that instantiations can also be performed programmatically. Nevertheless, given a list of all instantiations, I believe, it is nearly always easy to use the in-built search functionality on the imported theories to narrow down the exact place where the instantiation was performed.
I'm a vim fan, but only emacs has this Isabelle/HOL environment. jEdit is great, but I cannot use
using [[simp_trace=true]]
like in emacs.
How to enable "Tracing" in jEdit?
You can indeed use simp_trace in the middle of a proof in Isabelle/jEdit, like so:
lemma "(2 :: nat) + 2 = 4"
using [[simp_trace]]
apply simp
done
Alternatively, you can declare it globally, like so:
declare [[simp_trace]]
lemma "(2 :: nat) + 2 = 4"
apply simp
done
Both give you the simplifier's trace in the "Output" window when your cursor is just after the apply simp statement in jEdit.
If you need a trace depth deeper than 1 (default), you fine-tune it by
declare [[simp_trace_depth_limit=4]]
This example then gives a trace depth of 4.
As others have pointed out, you can use simp_trace. However, you can also use simp_trace_new combined with the "Simplifier Trace" window. This provides improved output over simp_trace:
lemma "rev (rev xs) = xs"
using [[simp_trace_new]]
apply(induction xs)
apply(auto)
done
To view the trace, position the cursor over "apply(auto)" then click on "See simplifier trace". The "Simplifier Trace" window(tab) should open. Click on "Show Trace" and a new window should appear showing a trace for each subgoal.
The Isabelle/Isar reference provides more details:
simp_trace_new controls Simplifier tracing within Isabelle/PIDE applications, notably Isabelle/jEdit.
This provides a hierarchical representation of the rewriting steps performed by the Simplifier.
Users can configure the behaviour by specifying breakpoints, verbosity
and enabling or disabling the interactive mode.
In normal verbosity (the default), only rule applications matching a breakpoint will be
shown to the user. In full verbosity, all rule applications will be logged.
Interactive mode interrupts the normal flow of the Simplifier and defers
the decision how to continue to the user via some GUI dialog.
Alternatively you can specify "using [[simp_trace_new mode=full]]" link here
To see all steps taken by the simplifier.
NOTE: in the previous example, showing the trace of "apply(induction xs)" yields no output.