Using syntax/translations wiith locales - isabelle

Is there any known hack that allows custom syntax for definitions inside a given locale, using the syntax/translation mechanism? All of my attempts at an "obvious" solution are generating type errors, which I am led to believe is caused by syntax/translation not yet being made "locale-aware".

Raw AST transformations with syntax and translations cannot be used inside locales in Isabelle2016. There is a workaround for constants and types whose declaration does not depend on locale parameters. You merely have to issue the syntax declaration outside of the locale for the appropriate constant from background theory. Below is a proof of concept:
locale test = fixes a :: nat begin
definition foo :: "nat ⇒ nat" where "foo x = x"
end
syntax "_foo" :: "nat ⇒ bool" ("FOO")
translations "FOO" ↽ "CONST test.foo"
context test begin
term foo
This workaround does not work for constants which depend on parameters of the locale, because then constant in the background theory takes these parameters as additional arguments and the locale installs an abbreviation, which is folded before the custom syntax translation fires.

Related

Syntactic abbreviation for types with in Isabelle/HOL?

Is there a way to define syntactic replacements for types in Isabelle/HOL?
I want to do something like this:
syntax my_short_list :: "type" ("my-list")
translations "my_short_list" ⇌ (type) "'a list" ― ‹Could not find syntax to express this ...›
locale foo =
fixes blub :: "my-list ⇒ my-list"
And want this to be interpreted like this:
locale foo =
fixes blub :: "'a list ⇒ 'a list"
(all occurrences of my-list are replaced with the text 'a list)
The above produces the following error:
Error in syntax translation rule: rhs contains extra variables
"my_short_list" ↝ ("\<^type>List.list" 'a)
So I am looking for a variant that is a purely syntactic replacement without any macro hygiene checks.
Some background to understand the underlying problem:
I have a locale with 5 type parameters and a datatype X that takes all 5 type parameters, so for every use I have to write ('a, 'b, 'c, 'd, 'e) X. Obviously the names are longer in practice so this becomed even more unreadable.
Other approaches I tried:
Defining the types or a type_synonym in my locale.
This is not allowed (see https://stackoverflow.com/a/19281758/303637).
Just using another type parameter to represent ('a, 'b, 'c, 'd, 'e) X and assumes for all the required properties of the data type.
This is a lot of boilerplate since I need to write down all the properties that are usually automatically generated for datatype definitions.

Isabelle/HOL: access interpretation in another locale

There's an Isabelle/HOL library that I want to build on with new definitions and proofs. The library defines locale2, which I'd like to build upon. Inside locale2, there's an interpretation of locale1.
To extend locale2 in a separate theory, I define locale3 = locale2. Inside locale3, however, I can't figure out how to access locale2's interpretation of locale1. How can I do that? (Am I even going about this in the right way at all?)
Below is an MWE. This is the library theory with the locale I want to extend:
theory ExistingLibrary
imports Main
begin
(* this is the locale with the function I want *)
locale locale1 = assumes True
begin
fun inc :: "nat ⇒ nat"
where "inc n = n + 1"
end
(* this is the locale that interprets the locale I want *)
locale locale2 = assumes True
begin
interpretation I: locale1
by unfold_locales auto
end
end
This is my extension theory. My attempt is at the bottom, causing an error:
theory MyExtension
imports ExistingLibrary
begin
locale locale3 = locale2
begin
definition x :: nat
where "x = I.inc 7" (* Undefined constant: "I.inc" *)
end
end
Interpretations inside a context last only until the end of the context. When the context is entered again, you have to repeat the interpretation to make the definitions and theorems available:
locale 3 = locale2 begin
interpretation I: locale1 <proof>
For this reason, I recommend to split the first interpretation step into two:
A lemma with a name that proves the goal of the interpretation step.
The interpretation command itself which can be proved by(rulelemma)
If you want the interpretation to take place whenever you open the locale and whenever you interpret the locale, then sublocale instead of interpretation might be better.

quotient_type warning "no map function"

When using the quotient_type command I get the following warning: "No map function defined for Example.A. This will cause problems later on".
Here is a minimal example to trigger the warning(tested with Isabelle2017).
theory Example
imports
Main
begin
datatype 'a A = B "'a A" | C
(*for map: map *) (* uncommenting doesn't fix the warning*)
quotient_type 'a Q = "'a A" / "op ="
by (rule identity_equivp)
end
So my questions are:
What is meant by a map function in this context (I only do know the concept of a map function in the context of functors in functional programming)?
What does it have to do with the datatype packages map functions, like one that would be generated by the commented line?
Which problems will one get later on?
The datatype command does not by default register the generated map function with the quotient package because there may be more general mappers (in case there are dead type variables). You therefore must do the functor declaration manually:
functor map_A
by(simp_all add: A.map_id0 A.map_comp o_def)
The mapper and its theorems are needed if you later want to lift definitions through the quotient type. This has been discussed on the Isabelle mailing list.

Using type classes to overload notation for constructors (now a namespace issue)

This is a derivative question of Existing constants (e.g. constructors) in type class instantiations.
The short question is this: How can I prevent the error that occurs due to free_constructors, so that I can combine the two theories that I include below.
I've been sitting on this for months. The other question helped me move forward (it appears). Thanks to the person who deserves thanks.
The real issue here is about overloading notation, though it looks like I now just have a namespace problem.
At this point, it's not a necessity, just an inconvenience that two theories have to be used. If the system allows, all this will disappear, but I ask anyway to make it possible to get a little extra information.
The big explanation here comes in explaining the motivation, which may lead to getting some extra information. I explain some, then include S1.thy, make a few comments, and then include S2.thy.
Motivation: using syntactic type classes for overloading notation of multiple binary datatypes
The basic idea is that I might have 5 different forms of binary words that have been defined with datatype, and I want to define some binary and hexadecimal notation that's overloaded for all 5 types.
I don't know what all is possible, but the past tells me (by others telling me things) that if I want code generation, then I should use type classes, to get the magic that comes with type classes.
The first theory, S1
Next is the theory S1.thy. What I do is instantiate bool for the type classes zero and one, and then use free_constructors to set up the notation 0 and 1 for use as the bool constructors True and False. It seems to work. This in itself is something I specifically wanted, but didn't know how to do.
I then try to do the same thing with an example datatype, BitA. It doesn't work because constant case_BitA is created when BitA is defined with datatype. It causes a conflict.
Further comments of mine are in the THY.
theory S1
imports Complex_Main
begin
declare[[show_sorts]]
(*---EXAMPLE, NAT 0: IT CAN BE USED AS A CONSTRUCTOR.--------------------*)
fun foo_nat :: "nat => nat" where
"foo_nat 0 = 0"
(*---SETTING UP BOOL TRUE & FALSE AS 0 AND 1.----------------------------*)
(*
I guess it works, because 'free_constructors' was used for 'bool' in
Product_Type.thy, instead of in this theory, like I try to do with 'BitA'.
*)
instantiation bool :: "{zero,one}"
begin
definition "zero_bool = False"
definition "one_bool = True"
instance ..
end
(*Non-constructor pattern error at this point.*)
fun foo1_bool :: "bool => bool" where
"foo1_bool 0 = False"
find_consts name: "case_bool"
free_constructors case_bool for "0::bool" | "1::bool"
by(auto simp add: zero_bool_def one_bool_def)
find_consts name: "case_bool"
(*found 2 constant(s):
Product_Type.bool.case_bool :: "'a∷type => 'a∷type => bool => 'a∷type"
S1.bool.case_bool :: "'a∷type => 'a∷type => bool => 'a∷type" *)
fun foo2_bool :: "bool => bool" where
"foo2_bool 0 = False"
|"foo2_bool 1 = True"
thm foo2_bool.simps
(*---TRYING TO WORK A DATATYPE LIKE I DID WITH BOOL.---------------------*)
(*
There will be 'S1.BitA.case_BitA', so I can't do it here.
*)
datatype BitA = A0 | A1
instantiation BitA :: "{zero,one}"
begin
definition "0 = A0"
definition "1 = A1"
instance ..
end
find_consts name: "case_BitA"
(*---ERROR NEXT: because there's already S1.BitA.case_BitA.---*)
free_constructors case_BitA for "0::BitA" | "1::BitA"
(*ERROR: Duplicate constant declaration "S1.BitA.case_BitA" vs.
"S1.BitA.case_BitA" *)
end
The second theory, S2
It seems that case_BitA is necessary for free_constructors to set things up, and it occurred to me that maybe I could get it to work by using datatype in one theory, and use free_constructors in another theory.
It seems to work. Is there a way I can combine these two theories?
theory S2
imports S1
begin
(*---HERE'S THE WORKAROUND. IT WORKS BECAUSE BitA IS IN S1.THY.----------*)
(*
I end up with 'S1.BitA.case_BitA' and 'S2.BitA.case_BitA'.
*)
declare[[show_sorts]]
find_consts name: "BitA"
free_constructors case_BitA for "0::BitA" | "1::BitA"
unfolding zero_BitA_def one_BitA_def
using BitA.exhaust
by(auto)
find_consts name: "BitA"
fun foo_BitA :: "BitA => BitA" where
"foo_BitA 0 = A0"
|"foo_BitA 1 = A1"
thm foo_BitA.simps
end
The command free_constructors always creates a new constant of the given name for the case expression and names the generated theorems in the same way as datatype does, because datatype internaly calls free_constructors.
Thus, you have to issue the command free_constructors in a context that changes the name space. For example, use a locale:
locale BitA_locale begin
free_constructors case_BitA for "0::BitA" | "1::BitA" ...
end
interpretation BitA!: BitA_locale .
After that, you can use both A0 and A1 as constructors in pattern matching equations and 0 and 1, but you should not mix them in a single equation. Yet, A0 and 0 are still different constants to Isabelle. This means that you may have to manually convert the one into the other during proofs and code generation works only for one of them. You would have to set up the code generator to replace A0 with 0 and A1 with 1 (or vice versa) in the code equations. To that end, you want to declare the equations A0 = 0 and A1 = 1 as [code_unfold], but you also probably want to write your own preprocessor function in ML that replaces A0 and A1 in left-hand sides of code equations, see the code generator tutorial for details.
Note that if BitA was a polymorphic datatype, packages such as BNF and lifting would continue to use the old set of constructors.
Given these problems, I would really go for the manual definition of the type as described in my answer to another question. This saves you a lot of potential issues later on. Also, if you are really only interested in notation, you might want to consider adhoc_overloading. It works perfectly well with code generation and is more flexible than type classes. However, you cannot talk about the overloaded notation abstractly, i.e., every occurrence of the overloaded constant must be disambiguated to a single use case. In terms of proving, this should not be a restriction, as you assume nothing about the overloaded constant. In terms of definitions over the abstract notation, you would have to repeat the overloading there as well (or abstract over the overloaded definitions in a locale and interpret the locale several times).

Can I overload the notation for operators that are assigned to bool and list?

(NOTE: If I can get rid of the warning I show below, then I say a bunch of extraneous stuff. As part of asking a question, I also do some opinionating. I guess that's sort of asking the question "Why am I wrong here in what I say?")
It seems that 6 of the symbols used for bool operators should have been assigned to syntactic type classes, and bool instantiated for those type classes. In particular, these:
~, &, |, \<not>, \<and>, \<or>.
Because type annotation of terms is a frequent requirement for HOL operators, I don't think it would be a great burden to have to use bool annotations for those 6 operators.
I would like to overload those 6 symbols for other logical operators. Not having the usual symbols for an application can result in there being no good solution for notation.
In the following example source, if I can get rid of the warnings, then the problem is solved (unless I would be setting a trap for myself):
definition natOP :: "nat => nat => nat" where
"natOP x y = x"
definition natlistOP :: "nat list => nat list => nat list" where
"natlistOP x y = x"
notation
natOP (infixr "&" 35)
notation
natlistOP (infixr "&" 35)
term "True & False"
term "2 & (2::nat)"
term "[2] & [(2::nat)]" (*
OUTPUT: Ambiguous input produces 3 parse trees:
...
Fortunately, only one parse tree is well-formed and type-correct,
but you may still want to disambiguate your grammar or your input.*)
Can I get rid of the warnings? It seems that since there's a type correct term, there shouldn't be a problem.
There are actually other symbols I also want, such as !, used for list:
term "[1,2,3] ! 1"
Here's the application for which I want the symbols:
Verilog HDL Operators.
Update
Based on Brian Huffman's answer, I unnotate &, and switch & to a syntactic type class. It'll work out, or it won't, indeed, binary logic, so diversely applicable. My general rule is "don't mess with default Isabelle/HOL".
(*|Unnotate; switch to a type class; see someday why this is a bad idea.|*)
no_notation conj (infixr "&" 35)
class conj =
fixes syntactic_type_classes_are_awesome :: "'a => 'a => 'a" (infixr "&" 35)
instantiation bool :: conj
begin
definition syntactic_type_classes_are_awesome_bool :: "bool => bool => bool"
where "p & q == conj p q"
instance ..
end
term "True & False"
value "True & False"
declare[[show_sorts]]
term "p & q" (* "(p::'a::conj) & (q::'a::conj)" :: "'a::conj" *)
You can "undeclare" special syntax using the no_notation command, e.g.
no_notation conj (infixr "\<and>" 35)
This infix operator is then available to be used with your own functions:
notation myconj (infixr "\<and>" 35)
From here on, \<and> refers to the constant myconj instead of the HOL library's standard conjunction operator for type bool. You should have no warnings about ambiguous syntax. The original HOL boolean operator is still accessible by name (conj), or you can give it a different syntax if you want with another notation command.
For the no_notation command to work, the pattern and fixities must be exactly the same as they were declared originally. See src/HOL/HOL.thy for the declarations of the operators you are interested in.
I should warn about a potential pitfall: Subsequent theory merges can bring the original syntax back into scope, causing ambiguous syntax again. For example, say your theory A.thy imports Main and redeclares the \<and> syntax. Then your theory B.thy imports both A and another library theory, say Complex_Main. Then in theory B, \<and> will be ambiguous. To prevent this, make sure to put all your external theory imports in the one theory file where you change the syntax; then have all of your other theories import this one.

Resources