Zulip Chat Archive
Stream: new members
Topic: basic tactic writing & IO
Logan Murphy (Oct 02 2020 at 17:15):
a slightly different question, is there any support for defining lean functions from standard input? For instance, is there a way to use get_line
to read a string like
fun n : nat, ne n 0
and turn this into a lean expression, which could be used in a tactic? I looked around in tactic.expr
and system.io
but I can't seem to find anything of this kind. I can write a simple parser for now, but it would be nice to know if there's a general function or something
Mario Carneiro (Oct 02 2020 at 18:44):
You can only do this from the parser
monad
Mario Carneiro (Oct 02 2020 at 19:05):
@Logan Murphy Here's a proof of concept:
import system.io
open lean lean.parser tactic interactive
meta def from_file {α} (f : string) (p : parser α) : parser α :=
do buf ← tactic.unsafe_run_io (io.fs.read_file f),
prod.fst <$> with_input p buf.to_string
meta def load_parser {α} (p : parser α) : parser α :=
do t ← types.texpr,
f ← ↑(to_expr t >>= eval_expr string),
from_file f p
meta def tactic.interactive.load (e : parse (load_parser types.texpr)) : tactic unit :=
interactive.exact e
example : true :=
begin
let foo := by load "foo.txt",
-- foo : ℕ → Prop := λ (n : ℕ), n ≠ 0
end
foo.txt:
λ n : ℕ, n ≠ 0
Logan Murphy (Oct 02 2020 at 19:06):
That’s very helpful Mario, thank you very much!
Mario Carneiro (Oct 02 2020 at 19:08):
Here's a version that doesn't do any file IO and just does the string -> expr part
open lean lean.parser tactic interactive
meta def parse_parser {α} (p : parser α) : parser α :=
do t ← types.texpr,
f ← ↑(to_expr t >>= eval_expr string),
prod.fst <$> with_input p f
meta def tactic.interactive.parse (e : parse (parse_parser types.texpr)) : tactic unit :=
interactive.exact e
example : true :=
begin
let foo := by parse "λ (n : ℕ), n ≠ 0",
-- foo : ℕ → Prop := λ (n : ℕ), n ≠ 0
end
Logan Murphy (Oct 05 2020 at 01:57):
I'm not super familiar yet with monads as a programming structure, would it be hard to write a version of io.iterate
as a tactic unit
? Just looking at how it's used in Lean 3 for Hackers, it would be useful to have a counter of some kind in an iteration.
Specifically, the proof I'm trying to automate is of the form
import system.io tactic data.list
open io tactic list
def aList : list Prop := [true, true, true]
example (H : ∀ i : fin aList.length, (aList.nth_le i.val i.property)) : false :=
begin
rw aList at H,
have h1 := H ⟨0, dec_trivial⟩,
have h2 := H ⟨1, dec_trivial⟩,
have h3 := H ⟨2, dec_trivial⟩,
end
It seems that the simplest way to automate this would be to iterate a counter i
from 0 to aList.length
(for any aList
)
tactic.interactive.iterate
allows one to specify the size of the loop, but not to refer to the counter variable
Mario Carneiro (Oct 05 2020 at 04:08):
It's easy to do unbounded iteration using a recursive function
Mario Carneiro (Oct 05 2020 at 04:09):
you don't need io
for that
Mario Carneiro (Oct 05 2020 at 04:09):
By the way, your proof looks a lot like fin_cases
Mario Carneiro (Oct 05 2020 at 04:18):
Here's a really literal interpretation of how you could automate that:
example (H : ∀ i : fin aList.length, (aList.nth_le i.val i.property)) : false :=
begin
rw aList at H,
(do (list.range 3).mmap' (λ i, do
let h : name := ("h" ++ to_string (i+1) : string),
tactic.interactive.«have» h none ```(H ⟨%%(reflect i), dec_trivial⟩))),
end
Logan Murphy (Oct 15 2020 at 22:56):
Hey @Mario Carneiro (or anyone), would you be able to help me understand how some of the parser code you sent works? I'm not familiar with using a monad for parsing.
import system.io tactic data.list
open io tactic list
open lean lean.parser tactic interactive
meta def from_file {α : Type}
(f : string)
(p : lean.parser α) : lean.parser α :=
do
buf ← unsafe_run_io (io.fs.read_file f), -- this is clear
x ← with_input p buf.to_string, -- not sure what this is doing
return (prod.fst x) -- returns something of type lean.parser (types.texpr)
meta def load_parser
{α : Type}
(p : lean.parser α) : lean.parser α :=
do t ← types.texpr, -- fine
f ← ↑(to_expr t >>= eval_expr string), -- not sure what this does
from_file f p
meta def tactic.interactive.load
(e : parse (load_parser types.texpr)) : tactic unit :=
interactive.exact e
Additionally, I'm wondering if there's a way to get expressions which are loaded by the parser in this fashion into the scope of a begin...end
block, in the following sense. I want to parse an expression from, say a file, and call a tactic from io.run_cmd
which sets the goal to be something containing that expression, and prove it. For instance, I'd like to be able to write something like
inductive even : ℕ → Prop
| zero : even 0
| add2 : ∀ n : ℕ, even n → even (n+2)
meta def silly : tactic unit :=
applyc `even.zero
-- input.txt :
-- 0
meta def main : io unit :=
let INPUT := by `[load "/path/to/input.txt"] in
io.run_tactic $ do `[have : even INPUT], silly,
return ()
But I don't think I can refer to things like INPUT outside the [ block ] like this.
Any help greatly appreciated :)
Logan Murphy (Oct 15 2020 at 23:01):
The whole idea being that i can call main
from some other process and see what Lean does with the contents of the file
Jason Rute (Oct 15 2020 at 23:12):
There are three ways you can get to io
: (1) A main : io unit
def, (2) inside a tactic monad via tactic.unsafe_run_io
and (3) inside a parser monad via parser.of_tactic t
where t
calls tactic.unsafe_run_io
. Only the parser has access to the parser monad. However, you can make your own custom lean command which runs the parser monad and that can then get information from a file and parse it. (You can even pass around the parser state to other monads so they can use it to parse stuff.) I need to think through your use case and then I can give you more advice with some code.
Jason Rute (Oct 15 2020 at 23:12):
Also, I have a fairly involved private WIP repo which uses these techniques to have lean communicate with stdin/stdout or with python as a subprocess. If you want an invite, I can give you one.
Jason Rute (Oct 15 2020 at 23:42):
Also, I should say that if you are only parsing simple things like numbers or such, you could also do one of the following:
- write your own parser in lean which parses a string from scratch into whatever type of object you like. Lean has a few built in operations already for converting strings to numbers, etc, for example
string.to_nat
. You probably want to usemeta
, but you shouldn't need to usetactic
,io
, orparse
monads unless you want to use them for convenience of error handling. - Use the library
data.buffer.parser
which despite its name is a separate parser monad fromlean.parser
. For example, I've used this to write a JSON parser. However, it is really slow and something Mario said, makes me think that this is a necessary consequence of using a parsing monad like this.
Both of these are available to be used inside the tactic
or io
monads. However, they don't have good support for parsing Lean expressions.
Logan Murphy (Oct 15 2020 at 23:49):
Thank you very much Jason, I'll digest this and let you know re: your repo :)
Jason Rute (Oct 16 2020 at 00:00):
I'll try to write code up some examples, but I don't know if I'll get to it tonight.
Jason Rute (Oct 16 2020 at 03:55):
Oh, I just looked at Mario's code that you posted. I'll comment what the pieces are doing and show you how to use it to set the goal. I was in the same place a few months ago. This was all very confusing. If you haven't read them, I also recommend the following background material:
- https://leanprover-community.github.io/extras/tactic_writing.html
- https://agentultra.github.io/lean-for-hackers/
Jason Rute (Oct 16 2020 at 13:54):
Here is a heavily commented version of Mario's code. Hopefully, this will help to understand what is going on. I tried to avoid too much fancy notation to make it a bit easier to follow.
import system.io
open io tactic list
open lean lean.parser tactic interactive
/-- Runs parser `p` on the contents of the file path `f`.
This parser is nonstandard. It ignores the buffer it
is supposed to parse and instead does an io operation to
read the contents of file path `f` into another buffer
and runs the parser `p` on that buffer. -/
meta def from_file {α : Type}
(f : string)
(p : lean.parser α) : lean.parser α :=
do
-- converts the io monad operation of reading a file
-- into a parser monad operation,
-- returning the file buffer.
buf ← unsafe_run_io (io.fs.read_file f),
-- with_input temporarily replaces the buffer being
-- parsed with a string (`buf.to_string` in this case)
-- and runs the parser `p` on that string.
(a, s) ← with_input p buf.to_string,
-- with_input returns a pair `(a, s)` where
-- `a` is the result of parser `p`
-- and `s` is the string that was parsed.
-- we only want to return the result `a`.
return a
/- This parser reads a file path from the buffer
(which will be a particular location in the lean
file where this is run)
as a quoted string, e.g. "myfile.txt", and
runs the parser `p` on the file contents. -/
meta def load_parser
{α : Type}
(p : lean.parser α) : lean.parser α :=
do
-- read a filename from the buffer
-- as a pre-expression
t ← types.texpr,
-- that pre-expression needs to be converted
-- a string. This is usually done at the
-- tactic level, but we need to stay at the parser
-- level to run the parser `p` later, so we do a
-- a computation in the tactic monad and lift it
-- back up to the parser monad.
f ← of_tactic (do
-- now we are in the tactic monad
-- convert the pre-expression `t` to an expression `e`
e <- to_expr t,
-- evaluate this expression as a string
s <- eval_expr string e,
-- return that string as the result of this tactic block
return s
),
-- run parser `p` on contents of file path `f`
e <- from_file f p,
return e
-- There are now two ways we can run our load_parser:
-- 1. As the parameter of an interactive tactic.
-- 2. Via a user command.
-- Here we do the first approach.
-- We will read a proof from a file and apply that
-- to our goal.
/-- This tactic will run a proof stored in a file.
Specifically `load "myfile.txt"` will load a proof
term saved in the file "myfile.txt" and use that
(inside an `exact` tactic) to complete the proof. -/
meta def tactic.interactive.load
-- In interative tactics, parameters take the form
-- `(<var> : parse <some_parser>)`
-- This means after the `load` tactic keyword, run
-- the parser `<some_parser>` to parse what the first
-- argument should be, and set `<var>` to be that value.
-- In this case, `e` will be the pre-expression
-- stored in the file path string which comes after
-- the `load` keyword.
(e : parse (load_parser types.texpr)) : tactic unit :=
-- take our expression e (which should be a proof term)
-- and attempt to prove the goal with it
interactive.exact e
example (x : ℕ) (h : x > 0) : ∃ y, y < x := begin
load "/tmp/my_file1.txt" -- exists.intro 0 h
end
Jason Rute (Oct 16 2020 at 13:57):
Also, an object of type parser α
is roughly a function of type parser_state -> parser_state × α
. The parser_state is essentially some buffer being parsed (i.e. usually the lean file) and the current position being read. (It also has some information about the environment and settings, but I'm ignoring that.) So a parser reads in a state and returns a new state and the parsed value. The monadic stuff hides the parser state so each line a <- my_parser
in the do
block is essentially saying run the parser on the buffer (possibly changing the parser state) and return the result a
. The final return a
is saying to return a : α
, but in actuality we aren't building an object of type α, but instead an object of type parser α
which is itself (roughly) a function. A parser is basically a delayed execution which will only return the result a
at the point when it has some actual buffer to parse. This happens when it is run as part of an interactive tactic.
Jason Rute (Oct 16 2020 at 13:57):
I'm also working on coding up your other request which reads from a file, sets the goal, and runs a tactic on that goal.
Logan Murphy (Oct 16 2020 at 21:27):
That's incredible Jason, thanks so much for taking the time to help me understand this!
Jason Rute (Oct 17 2020 at 01:25):
@Logan Murphy Here is one (of many) ways you can solve the second half of your question. As I said above, you can't access the lean parser framework inside the io monad, so you can't use it inside tactics or a main : io unit
. But you can use a user command instead. Here is an example (which reuses Mario's code above):
Jason Rute (Oct 17 2020 at 01:25):
inductive even : ℕ → Prop
| zero : even 0
| add2 : ∀ n : ℕ, even n → even (n+2)
-- we break down the task into a few helper
-- tactics. These might already be in mathlib.
/-- Sets the goal to the given expression,
ignoring the current goal. -/
meta def set_goal (goal : expr) : tactic unit :=
do
-- Set metavariable for our goal.
v <- mk_meta_var goal,
-- Replace goal list with our goal
set_goals [v],
return ()
/-- Returns tt if no more goals, else false -/
meta def is_solved : tactic bool :=
-- the pattern ... >> ... <|> ...
-- is like a monadic if then else
done >> return tt <|> return ff
/-- Replaces the current goals with `goal`,
applies `tac`, and returns if the goal is
solved. -/
meta def try_tactic_on_goal (tac: tactic unit)
(goal : expr) : tactic bool := do
-- We will ignore the current goal
-- (which is `|- true` inside a parser).
set_goal goal,
-- For debugging:
trace_state,
-- apply tac
try tac,
-- For debugging:
trace_state,
-- check if solved
-- (and since this is the last tactic,
-- in the do block, return it's result)
is_solved
/-- A user command. It is technically a
parser, but we will actually be using it
to run some code. However, since it is
a parser we can use our `from_file`
parser from above in the code. -/
@[user_command]
meta def main_app
(meta_info : decl_meta_info)
(_ : parse (tk "main_app")) : lean.parser unit :=
-- the above is user command boilerplate
-- below is the code
do
-- get contents of file as a pre-expression
t <- from_file "/tmp/my_file2.txt" types.texpr,
-- now that we are done with the parser,
-- everything else can be done in the tactic monad
of_tactic (do
e <- to_expr t,
n <- eval_expr ℕ e,
let goal := `(even n),
let tac := `[apply even.zero],
success <- try_tactic_on_goal tac goal,
if success then
trace "Solved!"
else
trace "Not solved :("
)
-- We put a period here to let lean know to stop
-- parsing the above definition. I don't know
-- exactly why it is needed.
.
-- /tmp/my_file2.txt 0
main_app -- |- even 0
-- Solved!
Jason Rute (Oct 17 2020 at 01:32):
You could run the above as a stand alone program with lean your_file.lean
. Right now it outputs via traces, but you could use the IO framework instead and output to stdout. (Traces are easier to work with in VS Code.)
Jason Rute (Oct 17 2020 at 02:18):
One thing you will notice about the above code is that all the work is done in the tactic monad, but we need the parser monad for just some key parsing tasks. One fancy trick I've discovered is that we could "steal" the parser state from the parser monad and give it as an argument to a tactic. This allows us to better organize the code. Here is what that would look like.
/-- Run a parser inside as a tactic.
It's unsafe in that you don't expect a
tactic to consume the buffer being parsed.
Best to only use with parsers that don't
consuming the buffer. -/
meta def unsafe_run_parser (ps : parser_state)
{α : Type} (p : parser α) : tactic α :=
-- Since parser and tactic frameworks are
-- the same, can replaces the parser state
-- with the tactic state.
λ ts : tactic_state, match (p ps) with
| result.success a _ := result.success a ts
| result.exception fmt pos _ := result.exception fmt pos ts
end
/-- The tactic which does all the work -/
meta def main_t (ps : parser_state) : tactic unit :=
do
-- now that I have a parser state,
-- I can do parser things inside tactics:
buf ← unsafe_run_io (io.fs.read_file "/tmp/my_file.txt"),
(t, s) ← unsafe_run_parser ps (
with_input types.texpr buf.to_string
),
e <- to_expr t,
n <- eval_expr ℕ e,
let goal := `(even n),
let tac := `[apply even.zero],
success <- try_tactic_on_goal tac goal,
if success then
trace "Solved!"
else
trace "Not solved :("
/-- Return the parser_state. -/
meta def get_state : parser parser_state :=
λ ps, result.success ps ps
@[user_command]
meta def main_app2
(meta_info : decl_meta_info)
(_ : parse (tk "main_app")) : lean.parser unit :=
do
ps <- get_state,
of_tactic (main_t ps)
Jason Rute (Oct 17 2020 at 02:20):
This trick is only good for user commands I think. I don't think you could use it in Mario's example.
Mario Carneiro (Oct 17 2020 at 02:37):
Well, whenever lean calls out to the parser monad you can do it. In my example I used tactic argument parsing to capture a parser_state
Jason Rute (Oct 17 2020 at 02:41):
But you can’t pass that state to the body of the interactive tactic. It would seem clearer to do the file IO in the tactic body.
Mario Carneiro (Oct 17 2020 at 02:42):
Actually you can, although I have no idea what would happen if you did
Mario Carneiro (Oct 17 2020 at 02:42):
you can just use parse read
where def read : parser parser_state := \lam s, (return s) s
Mario Carneiro (Oct 17 2020 at 02:44):
Oh, that probably doesn't work because parser_state
is not serializable
Mario Carneiro (Oct 17 2020 at 02:45):
it doesn't implement has_reflect
and I think this is required by parse
because it is used during the desugaring of the tactic block to an expression that will be eval_expr
'd
Jason Rute (Oct 17 2020 at 09:55):
Exactly.
Jason Rute (Oct 17 2020 at 14:15):
Sorry for all the messages. I've been thinking about this subject a lot. One final point I want to make (again), is that one often doesn't need the Lean parser as much as one thinks. The one thing the lean parser is really good at is parsing "user readable" Lean expressions with special notation, overloaded operators, etc. But usually what one wants to store in a file is data. Here are two common cases:
- store data in a standard format, e.g. JSON. This is what we were doing with the setting a goal example. We read in a number string and converted that to a number in Lean. Using the lean parser was overkill, since we could just use
string.to_nat
. I also just discovered that newer versions of Lean have a JSON parser built in. And even if it didn't you could write your own in Lean (I'll send you an example). This doesn't require the lean parser at all. - store Lean expressions as text. This is what Mario's example does. All we needed the lean parser for was to turn a string into an expression. But again, we could have stored that proof term in a more machine readable form. Lean IO has a few built-in ways to serialize and deserialize expressions to/from a file. Also, one can serialize an expression to a string. The built in ones might not suit your needs, but again it is not very hard to write a serializer and deserializer for expressions in Lean that don't require the lean parser (I'll also send you an example serializing and deserializing expressions into/from s-expressions).
Logan Murphy (Oct 19 2020 at 15:25):
This is great Jason, thank for being so thorough. This will be great for helping me move my project forward.
I'm seeing how I can use your code for the particular task I'm trying to automate. I think this is only tangentially related to what you wrote, but I'm basically trying to substitute some custom structures instead of the natural numbers in the example, and trying to figure out how to resolve a reflected
instance. I think from reading the docs that reflected
is closely related to has_repr
, but aside from that I'm not sure what I need to be doing in this case. Here are the structures I'm trying to use.
If Lean just needs some string formatting for the structure, I'm happy with just using some placeholder string for now, just don't know how to tell Lean what to use (if I understand the nature of the type class reflected
).
def Property (α : Type) : Type := α → Prop
structure Claim (α : Type) :=
(X : set α)
(P : Property α)
structure strategy (α : Type) :=
(Clm : Claim α)
(Props : list (Property α))
@[derive fintype]
inductive foo
| a
| b
| c
| d
def contra (α : Type)
(S : strategy foo ) : Prop :=
∀ x, ¬(S.Clm.P x) → (∃ i : fin (S.Props.length), (S.Props.nth_le i.1 i.2) x)
meta def silly_tactic : tactic unit :=
do
`[ intros n H,
fin_cases n,
simp at *,
exfalso, assumption,
use 0, simp, dsimp, refl,
use 1, simp, dsimp, refl,
use 2, simp, dsimp, refl]
--- path/to/input.txt file :
-- (strategy.mk
-- (Claim.mk {foo.a} (λ n, n = foo.a))
-- [(λ n, n=foo.b),
-- (λ n, n=foo.c),
-- (λ n, n=foo.d)])
@[user_command]
meta def main_app
(meta_info : decl_meta_info)
(_ : parse (tk "main_app")) : lean.parser unit :=
-- below is the code
do
-- get contents of file as a pre-expression
t <- from_file "/path/to/input.txt" types.texpr,
-- now that we are done with the parser,
-- everything else can be done in the tactic monad
of_tactic (do
e <- to_expr t,
S <- eval_expr (strategy foo) e,
let goal := `(contra foo S),
-- failed to synthesize type class instance for
-- ⊢ reflected (contra foo S)
let tac := `[silly_tactic],
success <- try_tactic_on_goal tac goal,
if success then
trace "Solved!"
else
trace "Not solved :("
)
Mario Carneiro (Oct 19 2020 at 15:33):
You can @[derive has_reflect]
on inductives
Logan Murphy (Oct 19 2020 at 15:43):
Still getting the same error, should I be making a local instance like
@[instance]
meta def contra_foo.refl {S : strategy foo} : has_reflect (contra foo S) := sorry
Mario Carneiro (Oct 19 2020 at 15:46):
what's the #mwe?
Mario Carneiro (Oct 19 2020 at 15:46):
the code above is missing imports and also doesn't have a failing example
Mario Carneiro (Oct 19 2020 at 15:51):
If the error you are talking about is the let goal
, you should try let goal := `(contra foo %%S),
, since S
is coming from the tactic context
Logan Murphy (Oct 19 2020 at 16:02):
Sorry, I think this is as minimal as I can get it right now
import system.io tactic
open io tactic list
open lean lean.parser tactic interactive
/-- Jason and Mario's parser code --/
meta def from_file {α : Type}
(f : string)
(p : lean.parser α) : lean.parser α :=
do
buf ← unsafe_run_io (io.fs.read_file f),
(a, s) ← with_input p buf.to_string,
return a
meta def load_parser
{α : Type}
(p : lean.parser α) : lean.parser α :=
do
t ← types.texpr,
f ← of_tactic (do
e <- to_expr t,
s <- eval_expr string e,
return s
),
e <- from_file f p,
return e
meta def tactic.interactive.load
(e : parse (load_parser types.texpr)) : tactic unit :=
interactive.exact e
meta def set_goal (goal : expr) : tactic unit :=
do
v <- mk_meta_var goal,
set_goals [v],
return ()
/-- My custom structures/types --/
def Property (α : Type) : Type := α → Prop
structure Claim (α : Type) :=
(X : set α)
(P : Property α)
structure strategy (α : Type) :=
(Clm : Claim α)
(Props : list (Property α))
@[derive [fintype, has_reflect]]
inductive foo
| a
| b
| c
| d
def contra (α : Type)
(S : strategy foo ) : Prop :=
∀ x, ¬(S.Clm.P x) → (∃ i : fin (S.Props.length), (S.Props.nth_le i.1 i.2) x)
meta def silly_tactic : tactic unit :=
do
`[ intros n H,
fin_cases n,
simp at *,
exfalso, assumption,
use 0, simp, dsimp, refl,
use 1, simp, dsimp, refl,
use 2, simp, dsimp, refl]
--- path/to/input.txt file :
-- (strategy.mk
-- (Claim.mk {foo.a} (λ n, n = foo.a))
-- [(λ n, n=foo.b),
-- (λ n, n=foo.c),
-- (λ n, n=foo.d)])
@[user_command]
meta def main_app
(meta_info : decl_meta_info)
(_ : parse (tk "main_app")) : lean.parser unit :=
-- below is the code
do
-- get contents of file as a pre-expression
t <- from_file "/path/to/input.txt" types.texpr,
-- now that we are done with the parser,
-- everything else can be done in the tactic monad
of_tactic (do
e <- to_expr t,
S <- eval_expr (strategy foo) e,
let goal := `(contra foo S),
-- failed to synthesize type class instance for
-- ⊢ reflected (contra foo S)
let tac := `[silly_tactic],
return ()
)
.
main_app
Mario Carneiro (Oct 19 2020 at 16:06):
I think you actually just need `(contra foo %%e),
and skip the eval_expr
Logan Murphy (Oct 19 2020 at 16:09):
Yes, that 's what I needed . Thank you Mario.
Logan Murphy (Oct 19 2020 at 16:10):
So by using the antiquotations, we postpone the evaluation of the expr
to when main_app
is invoked?
Logan Murphy (Oct 19 2020 at 16:11):
And it's the expr
that's reflected, not anything at the object level,e.g. any Prop
Mario Carneiro (Oct 19 2020 at 16:11):
No, we never evaluate anything here
Mario Carneiro (Oct 19 2020 at 16:12):
so we don't need has_reflect
Mario Carneiro (Oct 19 2020 at 16:12):
we're just splicing the expression e
we elaborated directly into the term we're making
Mario Carneiro (Oct 19 2020 at 16:13):
eval_expr
is a way for turning expressions into values of various types, and has_reflect
is a way to turn values back into expressions
Mario Carneiro (Oct 19 2020 at 16:13):
they are just canceling each other out here
Mario Carneiro (Oct 19 2020 at 16:14):
Now it's not entirely correct that they are inverses, for example the expression `(2 + 2 : nat)
gets eval_expr
'd into 4, which is then reflect
ed into `(4 : nat)
. So if you wanted to make sure your terms were "normalized" then this would be a way to do it
Mario Carneiro (Oct 19 2020 at 16:16):
but if you just use the expr as is you don't have to worry about all the ways in which eval_expr
fails, for example if it runs across something noncomputable like a variable or an axiom or a pi
Logan Murphy (Oct 19 2020 at 16:17):
Ah I see. Thank you.
Joe Palermo (S2'17) (Apr 12 2021 at 21:13):
Is there an easy way to modify a function : x -> tactic x, so that it becomes x -> x? For example, the function tactic.infer_type : expr -> tactic expr. I'd like to create a function that does the same thing but yields 'expr' instead of 'tactic expr'.
I'm aware of mjoin, but my understanding that requires 2 levels of structure in order to apply it (i.e. it's a form of concat).
If you're wondering what I'm actually trying to do, I'm trying to implement the tactic 'extract_theorems' below, and the implementation I thought of requires a modification to tactic.infer_type. I wouldn't be surprised if there's a much better way to implement 'extract_theorems 'as I'm just getting started with Lean metaprogramming and don't really know what I'm doing :P, so I'd appreciate any tips. Thanks!
import meta.expr
open tactic
set_option pp.all true
meta def process_thm (d : declaration) : option declaration :=
let n := d.to_name in
if ¬ d.is_trusted ∨ n.is_internal then none
else match d with
| declaration.defn _ _ _ _ _ _ := none
| t@(declaration.thm n ns e te) := some t
| declaration.cnst _ _ _ _ := none
| declaration.ax _ _ _ := none
end
meta def library_thms : tactic $ list declaration :=
environment.decl_filter_map <$> get_env <*> return process_thm
meta def get_all_proofs : tactic $ list expr :=
do library_thms >>= λ x, return (declaration.value <$> x)
meta def print_proofs : tactic unit :=
do get_all_proofs >>= λ x, tactic.trace x
meta def extract_theorems : list expr :=
do get_all_proofs >>= λ exprs, (<MODIFIER FOR INFER_TYPE> ∙ infer_type) <$> exprs
--exprs : list expr
--infer_type : expr -> tactic expr
--<MODIFIER FOR INFER_TYPE> ∙ infer_type : expr -> expr
--(<MODIFIER FOR INFER_TYPE> ∙ infer_type) <$> exprs : list expr
Yakov Pechersky (Apr 12 2021 at 21:22):
You can't really change a x -> tactic x
into a x -> x
. But you can definitely change a list (tactic expr)
into a tactic (list expr)
, using list.traverse
Yakov Pechersky (Apr 12 2021 at 21:22):
An exercise for you to get a feel for such monadic transformations would be to write your own implementation of list.traverse
Yakov Pechersky (Apr 12 2021 at 21:24):
Also, since you're writing things with a combo of >>=
, <$>
and return
, you might just switch to do notation
Yakov Pechersky (Apr 12 2021 at 21:25):
you already have two levels of structure here, you have the tactic monad and the list monad
Yakov Pechersky (Apr 12 2021 at 21:26):
you might want to restructure your code to have a
declaration -> expr
Yakov Pechersky (Apr 12 2021 at 21:26):
Because your get_all_proofs
is really a tactic.map (list.map declaration.value))
Yakov Pechersky (Apr 12 2021 at 21:27):
When you have a >>= λ x, return ...
, that is just <$>
Yakov Pechersky (Apr 12 2021 at 21:28):
Provably: docs#is_lawful_monad.bind_pure_comp_eq_map
Joe Palermo (S2'17) (Apr 13 2021 at 14:35):
@Yakov Pechersky Thanks for the recommendation to use list.traverse. With it I wrote up proofs >>= λ exprs, (list.traverse tactic.infer_type exprs)
to get theorem exprs from proof exprs. Then I merged things together using do notation:
meta def extract_theorems : tactic $ list string := do
library_theorem_decls <- return (environment.decl_filter_map <$> tactic.get_env <*> return process_thm),
proofs <- return (library_theorem_decls >>= λ x, return (declaration.value <$> x)),
theorems <- proofs >>= λ exprs, (list.traverse tactic.infer_type exprs),
return ((λ x: expr, x.to_string ++ "\n") <$> theorems)
I'd like to cleanup the line with "proofs <- ...", using tactic.map as recommended, but I get the error message "unknown identifier 'tactic.map'". I tried import tactic
, but apparently there is no tactic.map? Not sure what I'm doing wrong
Yakov Pechersky (Apr 13 2021 at 14:40):
If you have a
do
var <- return (op)
that is the same at
do
let var := op
Yakov Pechersky (Apr 13 2021 at 14:41):
There is no tactic.map
, I just meant functor.map
specialized to functor
, which you can just write as <$>
Yakov Pechersky (Apr 13 2021 at 14:43):
Your <*> return ...
can also be simplified to a <$>
: docs#is_lawful_applicative.seq_pure
Yakov Pechersky (Apr 13 2021 at 14:52):
What about something like:
meta def decl_to_theorem (d : declaration) : tactic expr := infer_type d.value
meta def extract_theorems : tactic $ list string := do
env <- tactic.get_env
let ds := env.decl_filter_map process_thm
theorems <- ds.traverse decl_to_theorem
return ((λ x: expr, x.to_string ++ "\n") <$> theorems)
Joe Palermo (S2'17) (Apr 13 2021 at 14:59):
That's much cleaner! Thank you
Yakov Pechersky (Apr 13 2021 at 15:05):
Modulo any errors, I'm just guessing the types/API here. One might say, why not just make a decl -> tactic string
and print over those?
Joe Palermo (S2'17) (Apr 13 2021 at 15:07):
Yeah that would be a reasonable way to structure it too
Yakov Pechersky (Apr 13 2021 at 15:07):
I gather you'll be printing these. I think you should just use list.intersperse theorems "\n"
, that way you're not just doing the many O(n) list appends, since you're just relying on the "\n" for nice printing
Yakov Pechersky (Apr 13 2021 at 15:08):
Then you might factor out env <- tactic.get_env
to make a env -> tactic (list string)
, because you might be doing other things with an env
Yakov Pechersky (Apr 13 2021 at 15:08):
So that you don't have to tactic.get_env
elsewhere (if that is an expensive computation, idk if it is)
Last updated: Dec 20 2023 at 11:08 UTC