Zulip Chat Archive

Stream: general

Topic: notational leakage


Kevin Buzzard (Jul 04 2019 at 10:21):

My understanding of idiomatic Lean is that a lot of simple terms have some kind of "canonical form", and when writing Lean code (especially simp lemmas) one should write each such term in its canonical form rather than any of the defeq other forms which can be used, because defeq is fine for rfl but not for rw.

In the past, when things have not come out in the "canonical form" which I want them to be, this was user error, because this user in particular had no idea about this canonical form philosophy until recently.

I'm now attempting to stick to it rigidly whilst writing teaching materials. Here is an example where I decide that 0 (i.e. has_zero.zero) is my canonical form for mynat.zero and then a mynat.zero appears:

inductive mynat
| zero : mynat
| succ : mynat  mynat

-- I want to never see mynat.zero again
instance : has_zero mynat := mynat.zero

namespace mynat

definition add : mynat  mynat  mynat
| n 0 := n
| n (succ p) := succ (add n p)

instance : has_add mynat := mynat.add

theorem zero_add :  x : mynat, 0 + x = x :=
begin
  intro x,
  induction x with d hd,
  { -- ⊢ 0 + zero = zero -- leakage of "zero" i.e. mynat.zero
    sorry
  },
  { -- inductive case is fine
    sorry
  }
end

/-
⊢ @eq.{1} mynat (@has_add.add.{0} mynat mynat.has_add (@has_zero.zero.{0} mynat mynat.has_zero) mynat.zero) mynat.zero
-/

I was thinking I could just fix this with my own induction function, which does all the rewriting of mynat.zero to has_zero.zero. But I can't do it, because the induction tactic eats some new variable names and this is convenient, so I would have to write my own tactic to do this. I don't know where to start with writing tactics though. I think that in practice all I want to do is some kind of repeat {rw (show mynat.zero = (0 : mynat), from rfl) at *}, after every application of any tactic which leaks.

Does anyone have any advice on how to make Lean a bit more noob-friendly here? I just want it to work for beginners and them never ever see zero.

Johan Commelin (Jul 04 2019 at 10:25):

Also, this doesn't seem to happen when you reprove zero_add for Lean's version of nat, right?

Johan Commelin (Jul 04 2019 at 10:25):

Let me check.

Johan Commelin (Jul 04 2019 at 10:26):

1 goal
case nat.zero
 0 + 0 = 0

Johan Commelin (Jul 04 2019 at 10:27):

theorem zero_add' :  x : , 0 + x = x :=
begin
  intro x,
  induction x with d hd,
  { -- ⊢ 0 + 0 = 0 -- no leakage of "zero"
    sorry
  },
  { -- inductive case is fine
    sorry
  }
end

Johan Commelin (Jul 04 2019 at 10:27):

So somehow the induction tactic picked up that nat.zero is not canonical form, and it should use 0.

Mario Carneiro (Jul 04 2019 at 10:28):

It's not that hard to use a custom recursor, including getting names for the variables in the cases. You just use refine:

refine mynat.rec_on' n _ (λ m IH, _),

Mario Carneiro (Jul 04 2019 at 10:29):

This doesn't happen for nat because the pretty printer has magic to print nat.zero as 0 even though it's not the same as has_zero.zero nat

Mario Carneiro (Jul 04 2019 at 10:30):

so the leakage is still happening, you just can't see it because of the pp

Mario Carneiro (Jul 04 2019 at 10:32):

We could try writing an induction' which doesn't have a broken using clause. I never use it because the refine version is shorter, but if the using clause was triggered automatically with an attribute that could be a lot of value added for custom recursors

Johan Commelin (Jul 04 2019 at 10:33):

I think this could be quite helpful to beginners...

Johan Commelin (Jul 04 2019 at 10:33):

In some sense the induction tactic is quite useless as it is.

Johan Commelin (Jul 04 2019 at 10:34):

I would wish that refine blah.rec_on _ _ _ isn't "idiomatic". Because the informal proof says "use induction on x". It would be great if you could actually type induction x in the Lean proof.

Johan Commelin (Jul 04 2019 at 10:36):

And I wouldn't want a using clause, that's very verbose. I'd rather have some sort of attribute.

Johan Commelin (Jul 04 2019 at 10:36):

If we tag mynat.custom_rec_on with @induction then the induction' tactic could pick that up, right?

Mario Carneiro (Jul 04 2019 at 10:37):

that's the idea

Mario Carneiro (Jul 04 2019 at 10:37):

I like refine because it's the swiss army knife of lean tactics

Mario Carneiro (Jul 04 2019 at 10:38):

it's very straightforward to use and less to remember

Mario Carneiro (Jul 04 2019 at 10:38):

and it doesn't have gotchas like apply

Johan Commelin (Jul 04 2019 at 10:41):

Sure refine is great as Swiss army knife. But it doesn't give you the semantics that induction has.

Johan Commelin (Jul 04 2019 at 10:42):

If you want people to see in a glance what the idea behind the proof is...

Johan Commelin (Jul 04 2019 at 10:42):

Of course you can get almost every where with refine, rw and maybe occasionally a simp [foo,bar].

Johan Commelin (Jul 04 2019 at 10:43):

But if you want to write "beautiful, beginner-friendly proofs"...

Kevin Buzzard (Jul 05 2019 at 10:19):

What I need are non-leaky tactics, maybe called induction' and cases', so that

induction' n with d hd,

is the same as

  induction n with d hd,
  repeat {all_goals {rw (show mynat.zero = (0 : mynat), from rfl) at *}},
  repeat {all_goals {rw (show mynat.le = @has_le.le mynat _, from rfl) at *}},

etc etc

and induction' should fail iff induction fails.

Are these trivial to write?

Kevin Buzzard (Jul 05 2019 at 10:25):

I also want a rewrite tactic which does not try rfl at the end. Is this already in Lean?

Chris Hughes (Jul 05 2019 at 10:26):

Does this work?

namespace tactic.interactive

open tactic.interactive interactive.types expr lean lean.parser tactic interactive

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
`[repeat {all_goals {rw (show mynat.zero = (0 : mynat), from rfl) at *}},
  repeat {all_goals {rw (show mynat.le = @has_le.le mynat _, from rfl) at *}}]

end tactic.interactive

Chris Hughes (Jul 05 2019 at 10:27):

You could also write a recursor using has_zero.zero and do induction ... using

Mario Carneiro (Jul 05 2019 at 10:28):

It would be better to use change mynat.zero with 0 at * instead of rw rfl like that

Kevin Buzzard (Jul 05 2019 at 10:49):

For the rw with no refl I just copied a bunch of private defs from core and then deleted a random line and I think I got lucky:

import tactic.interactive

open lean
open lean.parser

local postfix `?`:9001 := optional
local postfix *:9001 := many

namespace tactic
namespace interactive
open interactive interactive.types expr

private meta def resolve_name' (n : name) : tactic expr :=
do {
  p  resolve_name n,
  match p with
  | expr.const n _ := mk_const n -- create metavars for universe levels
  | _              := i_to_expr p
  end
}

private meta def rw_goal (cfg : rewrite_cfg) (rs : list rw_rule) : tactic unit :=
rs.mmap' $ λ r, do
 save_info r.pos,
 eq_lemmas  get_rule_eqn_lemmas r,
 orelse'
   (do e  to_expr' r.rule, rewrite_target e {symm := r.symm, ..cfg})
   (eq_lemmas.mfirst $ λ n, do e  mk_const n, rewrite_target e {symm := r.symm, ..cfg})
   (eq_lemmas.empty)

private meta def uses_hyp (e : expr) (h : expr) : bool :=
e.fold ff $ λ t _ r, r || to_bool (t = h)

private meta def rw_hyp (cfg : rewrite_cfg) : list rw_rule  expr  tactic unit
| []      hyp := skip
| (r::rs) hyp := do
  save_info r.pos,
  eq_lemmas  get_rule_eqn_lemmas r,
  orelse'
    (do e  to_expr' r.rule, when (not (uses_hyp e hyp)) $ rewrite_hyp e hyp {symm := r.symm, ..cfg} >>= rw_hyp rs)
    (eq_lemmas.mfirst $ λ n, do e  mk_const n, rewrite_hyp e hyp {symm := r.symm, ..cfg} >>= rw_hyp rs)
    (eq_lemmas.empty)

private meta def rw_core (rs : parse rw_rules) (loca : parse location) (cfg : rewrite_cfg) : tactic unit :=
match loca with
| loc.wildcard := loca.try_apply (rw_hyp cfg rs.rules) (rw_goal cfg rs.rules)
| _            := loca.apply (rw_hyp cfg rs.rules) (rw_goal cfg rs.rules)
end >> (returnopt rs.end_pos >>= save_info <|> skip)

meta def rw' (q : parse rw_rules) (l : parse location) (cfg : rewrite_cfg := {}) : tactic unit :=
propagate_tags (rw_core q l cfg)

end interactive
end tactic

Is this a silly way to do it? I just deleted one of the >> lines after end in rw_core

Mario Carneiro (Jul 05 2019 at 11:04):

like I said, use change with instead of rw

Kenny Lau (Jul 05 2019 at 11:04):

use erw

Kevin Buzzard (Jul 05 2019 at 11:25):

change with just looks more complicated than rw. I have a working rw'. I don't mind writing new tactics. To be honest I don't even mind modding core Lean for this project, it's a standalone thing.

Kevin Buzzard (Jul 05 2019 at 11:25):

Chris I'll try your induction code when I get back in front of Lean. Thanks!

Johan Commelin (Jul 05 2019 at 11:39):

@Kevin Buzzard I think Mario means that you should use change with inside induction' instead of using rw there.

Kevin Buzzard (Jul 05 2019 at 11:39):

I just want one tactic which is simple to apply and which doesn't leak. I am not sure I understand what is being suggested.

Kevin Buzzard (Jul 05 2019 at 11:40):

At the minute I am writing

induction n with d hd,
clear_up_leaks,
{ ... } { ... }

Kevin Buzzard (Jul 05 2019 at 11:40):

and I want to write

induction' n with d hd
{ -- no leaks here}
{ -- or here}

Johan Commelin (Jul 05 2019 at 11:46):

Right, and Mario says that clear_up_leaks should use change instead of rw.

Johan Commelin (Jul 05 2019 at 11:47):

We might want an attribute @antileak or something like that, that will change terms into defeq terms that are more canonical.

Johan Commelin (Jul 05 2019 at 11:47):

I guess we could also use dsimp for this?

Johan Commelin (Jul 05 2019 at 11:48):

@Kevin Buzzard what happens if you write a simp lemma (tagged with @[simp] that says mynat.zero = 0 := rfl.
And then you call induction n with d hd; dsimp.

Johan Commelin (Jul 05 2019 at 11:49):

Similarly you will want mynat.succ n = n + 1 := rfl.

Johan Commelin (Jul 05 2019 at 11:49):

Is that sufficient to clear up the leaks?

Kevin Buzzard (Jul 05 2019 at 11:49):

That's strictly worse than clear_up_leaks because formally it's the same thing ("apply a tactic and then type something which you don't want") and secondly simp will close various goals, like rfl goals -- it does too much.

Mario Carneiro (Jul 05 2019 at 12:34):

using change with will not close any goals

Mario Carneiro (Jul 05 2019 at 12:34):

And I am talking about the definition of induction', like Johan says

Mario Carneiro (Jul 05 2019 at 12:49):

This works:

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
all_goals `[
  try {change mynat.zero with (0 : mynat) at *},
  try {change mynat.le with () at *},
  try {change @mynat.succ with (λ n, n + 1) at *, dsimp only at *}]

theorem zero_add :  x : mynat, x = x :=
begin
  intro x,
  induction' x with d hd,
  -- ⊢ 0 + 0 = 0
  -- 0 + d = d ⊢ 0 + (d + 1) = d + 1
end

Unfortunately change with does not support arguments in the lemmas, so I use some beta reduction at the end there. I don't think it will use rfl at the end though. Also propagate_tags undoes the work for some reason, so you can't use case with this

Kevin Buzzard (Jul 05 2019 at 12:56):

So this induction' thing, and rw' and also this structure_helper thing are all attempts to write more user-friendly ways of doing stuff. The idea is that we are shielding the users from Lean's weak spots here. And we're also shielding the user from definitions and recursion; I want to ask them to only prove theorems.

Mario Carneiro (Jul 05 2019 at 13:02):

Yes, but you could say that about any tactic

Scott Morrison (Jul 05 2019 at 14:56):

I agree you can say this about any tactic, but Kevin is implicitly making a point dear to my heart here --- whenever something is even slightly painful, we should grind away on it, by building suitable tactics, until it becomes smooth and easy. The payoff for building tactics that make life easier is really high!

Kevin Buzzard (Jul 05 2019 at 14:57):

Both Scott and I are speaking as mathematicians who have to deal with questions from undergrads of the form "why doesn't this rewrite work?" and we give technical answers involving pp.all but we'd rather the rewrite just worked ;-)

Kevin Buzzard (Jul 05 2019 at 17:37):

This works:

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
all_goals `[
  try {change mynat.zero with (0 : mynat) at *},
  try {change mynat.le with () at *},
  try {change @mynat.succ with (λ n, n + 1) at *, dsimp only at *}]

What do I need to open to make this compile?

unknown identifier 'parse'
unknown identifier 'cases_arg_p'
unknown identifier 'parse'
unknown identifier 'with_ident_list'

Regarding succ, are you suggesting that n + 1 is the canonical way of writing succ n? I had assumed it was the other way around.

Mario Carneiro (Jul 05 2019 at 17:58):

inductive mynat
| zero : mynat
| succ : mynat  mynat

-- I want to never see mynat.zero again
instance : has_zero mynat := mynat.zero

instance : has_one mynat := mynat.succ 0

namespace mynat

definition add : mynat  mynat  mynat
| n 0 := n
| n (succ p) := succ (add n p)

instance : has_add mynat := mynat.add

end mynat

namespace tactic.interactive

open tactic.interactive interactive.types expr lean lean.parser tactic interactive

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
all_goals `[
  try {change mynat.zero with (0 : mynat) at *},
  try {change mynat.le with () at *},
  try {change @mynat.succ with (λ n, n + 1) at *, dsimp only at *}]

end tactic.interactive

namespace mynat

theorem zero_add :  x : mynat, x = x :=
begin
  intro x,
  induction' x with d hd,
  -- ⊢ 0 + 0 = 0
  -- 0 + d = d ⊢ 0 + (d + 1) = d + 1
end

Kevin Buzzard (Jul 05 2019 at 19:28):

Thanks. What I am surprised about is that you want to replace succ with + 1. I decided in my development that succ x was more canonical than x + 1. Is there anything wrong with this decision?

Johan Commelin (Jul 05 2019 at 19:33):

I guess that it doesn't look very "mathematical"...

Mario Carneiro (Jul 05 2019 at 19:54):

I don't really care one away or another... the lean magic is pretty strong in being able to conflate them for nat. The only downside of n+1 is simp has a nasty habit of changing it to 1+n

Johan Commelin (Jul 05 2019 at 19:55):

Isn't that because add_comm is a simp-lemma for some reason?

Johan Commelin (Jul 05 2019 at 19:55):

I've seen it show up in the results of squeeze_simp.

Keeley Hoek (Jul 06 2019 at 04:18):

I'm adamant that definitions should be able have attached a way to format themselves, and probably implementing this with an attribute would work really well. Then e.g. once has_zero is defined zero can remember it is always 0, and there is no chasing of potential leakage everywhere. Another nice thing would be for vector_space vs module; if the scalar ring is (in expr land) an application of discrete_field.to_ring then print vector_space, else print module, etc.

Kevin Buzzard (Jul 07 2019 at 10:37):

Surprisingly, this modified tactic still leaks (although I can't see why):

inductive mynat
| zero : mynat
| succ : mynat  mynat

-- I want to never see mynat.zero again
instance : has_zero mynat := mynat.zero

instance : has_one mynat := mynat.succ 0

namespace mynat

definition add : mynat  mynat  mynat
| n 0 := n
| n (succ p) := succ (add n p)

instance : has_add mynat := mynat.add

end mynat

namespace tactic.interactive

open tactic.interactive interactive.types expr lean lean.parser tactic interactive

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
all_goals `[
  try {change mynat.zero with (0 : mynat) at *},
  try {change mynat.le with () at *},
  try {change @mynat.succ with (λ n, n + 1) at *, dsimp only at *}]

end tactic.interactive

namespace mynat

lemma eq_zero_of_add_right_eq_self {{a b : mynat}} : a + b = a  b = 0 :=
begin
  intro h,
  induction' a with a ha,
  { -- leakage
    -- h : zero + b = zero
    sorry
  }, sorry
end

end mynat

Mario Carneiro (Jul 07 2019 at 10:40):

what changed?

Mario Carneiro (Jul 07 2019 at 10:41):

the tactic looks the same

Kevin Buzzard (Jul 07 2019 at 10:41):

Oh -- I can see why!

example (x : mynat) (h : mynat.zero + x = x + (0 : mynat)) : false :=
begin
  -- h : zero + x = x + 0
    change mynat.zero with (0 : mynat) at *,
  -- h : zero + x = x + 0
    change mynat.zero with (0 : mynat) at h,
  -- h : 0 + x = x + 0
  sorry
end

Is this a bug in change @Mario Carneiro ?

Kevin Buzzard (Jul 07 2019 at 10:42):

[sorry -- by "this modified tactic" I mean "your tactic", not "I modified your tactic"]

Mario Carneiro (Jul 07 2019 at 10:43):

that's interesting. It looks like a bug (I mean it's definitely not intended behavior)

Kevin Buzzard (Jul 07 2019 at 10:45):

sigh

Johan Commelin (Jul 07 2019 at 10:45):

We need formally verified tactics

Kevin Buzzard (Jul 07 2019 at 10:51):

My workaround:

try {rw' (show mynat.zero = (0 : mynat), from rfl) at *},

where rw' is my modded rw which doesn't try to close goals with rfl

Mario Carneiro (Jul 07 2019 at 10:51):

It's written in lean, so you can just fix it

Mario Carneiro (Jul 07 2019 at 10:52):

We need formally verified tactics

You joke, but...

Kevin Buzzard (Jul 07 2019 at 10:52):

I can't understand meta code so I can't.

/--
`change u` replaces the target `t` of the main goal to `u` provided that `t` is well formed with respect to the local context of the main goal and `t` and `u` are definitionally equal.

`change u at h` will change a local hypothesis to `u`.

`change t with u at h1 h2 ...` will replace `t` with `u` in all the supplied hypotheses (or `*`), or in the goal if no `at` clause is specified, provided that `t` and `u` are definitionally equal.
-/
meta def change (q : parse texpr) : parse (tk "with" *> texpr)?  parse location  tactic unit
| none (loc.ns [none]) := do e  i_to_expr q, change_core e none
| none (loc.ns [some h]) := do eq  i_to_expr q, eh  get_local h, change_core eq (some eh)
| none _ := fail "change-at does not support multiple locations"
| (some w) l :=
  do u  mk_meta_univ,
     ty  mk_meta_var (sort u),
     eq  i_to_expr ``(%%q : %%ty),
     ew  i_to_expr ``(%%w : %%ty),
     let repl := λe : expr, e.replace (λ a n, if a = eq then some ew else none),
     l.try_apply
       (λh, do e  infer_type h, change_core (repl e) (some h))
       (do g  target, change_core (repl g) none)

Mario Carneiro (Jul 07 2019 at 10:55):

I don't see why at * would fail and at h would succeed. The last three lines are basically executing change ... at x, change ... at h, change ... at |-

Kevin Buzzard (Jul 07 2019 at 10:56):

Can you reproduce? My post should be a MWE

Kevin Buzzard (Jul 07 2019 at 10:56):

sorry, that's nonsense.

Kevin Buzzard (Jul 07 2019 at 10:56):

inductive mynat
| zero : mynat
| succ : mynat  mynat

-- I want to never see mynat.zero again
instance : has_zero mynat := mynat.zero

instance : has_one mynat := mynat.succ 0

namespace mynat

definition add : mynat  mynat  mynat
| n 0 := n
| n (succ p) := succ (add n p)

instance : has_add mynat := mynat.add

end mynat

namespace tactic.interactive

open tactic.interactive interactive.types expr lean lean.parser tactic interactive

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
all_goals `[
  try {change mynat.zero with (0 : mynat) at *},
  try {change mynat.le with () at *},
  try {change @mynat.succ with (λ n, n + 1) at *, dsimp only at *}]

end tactic.interactive

namespace mynat

lemma eq_zero_of_add_right_eq_self {{a b : mynat}} : a + b = a  b = 0 :=
begin
  intro h,
  induction' a with a ha,
  { -- leakage
    -- h : zero + b = zero
    change mynat.zero with (0 : mynat) at *,
    sorry
  }, sorry
end

example (x : mynat) (h : mynat.zero + x = x + (0 : mynat)) (h2 : 2 + 2 = 4) : false :=
begin
  -- h : zero + x = x + 0
    change mynat.zero with (0 : mynat) at *,
  -- h : zero + x = x + 0
    change mynat.zero with (0 : mynat) at h,
  -- h : 0 + x = x + 0
  sorry
end

end mynat

Kevin Buzzard (Jul 07 2019 at 10:57):

That's a WE

Mario Carneiro (Jul 07 2019 at 11:22):

Aha, I found the problem. Here's a standalone tactic that does the same thing as the change with line, with irrelevant things removed:

open tactic expr
example (x : mynat) (h : mynat.zero + x = x + (0 : mynat)) : false :=
begin
  -- h : zero + x = x + 0
  (do let repl := λe : expr, e.replace (λ a n,
      if a = `(mynat.zero) then some `(0 : mynat) else none),
    let hyp_tac (h) := (do
      e  infer_type h,
      num_reverted :   revert h,
      expr.pi n bi d b  target,
      tactic.change $ expr.pi n bi (repl e) b,
      intron num_reverted),
    [x, h]  tactic.local_context,
    tactic.try_lst $ [x, /- <- delete -/ h].map hyp_tac),
end

Mario Carneiro (Jul 07 2019 at 11:22):

The tactic fails in this case, but if you remove the x, on the last line then it works

Kenny Lau (Jul 07 2019 at 11:23):

run_cmd mk_simp_attr `leakage

namespace tactic.interactive

open tactic.interactive interactive.types expr lean lean.parser tactic interactive

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
all_goals `[try { dsimp only with leakage at * }]

end tactic.interactive


inductive mynat
| zero : mynat
| succ : mynat  mynat

instance : has_zero mynat := mynat.zero
-- I want to never see mynat.zero again
@[leakage] theorem mynat_zero_eq_zero : mynat.zero = 0 := rfl

instance : has_one mynat := mynat.succ 0

namespace mynat

definition add : mynat  mynat  mynat
| n 0 := n
| n (succ p) := succ (add n p)

instance : has_add mynat := mynat.add
-- I want to never see mynat.succ again
@[leakage] theorem mynat_succ_eq_add_one (n) : mynat.succ n = n+1 := rfl

lemma eq_zero_of_add_right_eq_self {{a b : mynat}} : a + b = a  b = 0 :=
begin
  intro h,
  induction' a with a ha,
  { -- no leakage
    -- h : 0 + b = 0
    sorry
  }, sorry
end

example (x : mynat) (h : mynat.zero + x = x + (0 : mynat)) (h2 : 2 + 2 = 4) : false :=
begin
  -- h : zero + x = x + 0
    change mynat.zero with (0 : mynat) at *,
  -- h : zero + x = x + 0
    change mynat.zero with (0 : mynat) at h,
  -- h : 0 + x = x + 0
  sorry
end

end mynat

Kevin Buzzard (Jul 07 2019 at 11:24):

I like the look of this

Johan Commelin (Jul 07 2019 at 11:26):

We need formally verified tactics

You joke, but...

What makes you think that I'm joking :grinning:

Mario Carneiro (Jul 07 2019 at 11:27):

The reason it fails is because the change ... at x, while not doing anything to the type, changes the internal name of the local variable because it is reverted and re-introduced, and this also changes the variable h so that the old reference is dangling and fails to achieve the desired result

Kevin Buzzard (Jul 07 2019 at 11:29):

@Kenny Lau can you do me a cases' too?

Kevin Buzzard (Jul 07 2019 at 11:29):

Currently I just cut and paste the cases tactic and then add stuff to the end

Kevin Buzzard (Jul 07 2019 at 11:29):

do tactic.interactive.induction hp none ids none, -- it's that line I need for cases

Mario Carneiro (Jul 07 2019 at 11:30):

Alternatively, if only the second line is a tactic, then you can write cases foo, unleak

Kenny Lau (Jul 07 2019 at 11:30):

run_cmd mk_simp_attr `leakage

namespace tactic.interactive

open tactic.interactive interactive.types expr lean lean.parser tactic interactive

meta def induction' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.induction hp none ids none,
all_goals `[try { dsimp only with leakage at * }]

meta def cases' (hp : parse cases_arg_p) (ids : parse with_ident_list) : tactic unit :=
do tactic.interactive.cases hp ids,
all_goals `[try { dsimp only with leakage at * }]

end tactic.interactive


inductive mynat
| zero : mynat
| succ : mynat  mynat

instance : has_zero mynat := mynat.zero
-- I want to never see mynat.zero again
@[leakage] theorem mynat_zero_eq_zero : mynat.zero = 0 := rfl

instance : has_one mynat := mynat.succ 0

namespace mynat

definition add : mynat  mynat  mynat
| n 0 := n
| n (succ p) := succ (add n p)

instance : has_add mynat := mynat.add
-- I want to never see mynat.succ again
@[leakage] theorem mynat_succ_eq_add_one (n) : mynat.succ n = n+1 := rfl

lemma eq_zero_of_add_right_eq_self {{a b : mynat}} : a + b = a  b = 0 :=
begin
  intro h,
  cases' a with n,
  { -- no leakage
    -- h : 0 + b = 0
    sorry },
  { -- no leakage
    -- h : n + 1 + b = n + 1
    sorry }
end

end mynat

Mario Carneiro (Jul 07 2019 at 11:31):

I'm not sure I like this "duplicate every tactic" approach

Kenny Lau (Jul 07 2019 at 11:32):

me neither

Kenny Lau (Jul 07 2019 at 11:32):

unleak is good

Kenny Lau (Jul 07 2019 at 11:32):

Alternatively, if only the second line is a tactic, then you can write cases foo, unleak

cases foo; unleak

Mario Carneiro (Jul 07 2019 at 11:32):

your version unleaks everywhere so it's the same

Mario Carneiro (Jul 07 2019 at 11:33):

but it's probably more idiomatic to drop the all_goals and use ;

Rob Lewis (Jul 07 2019 at 11:39):

There's an (interactive) change' tactic that's supposted to deal with the naming issue in change. https://github.com/leanprover-community/mathlib/pull/712

Kenny Lau (Jul 07 2019 at 11:39):

or just write dsimp only with leakage

Kenny Lau (Jul 07 2019 at 11:40):

(accidental promotion of simp attributes)

Kenny Lau (Jul 07 2019 at 11:40):

(I've always been adamant about the widespread use of simp attribute)

Keeley Hoek (Jul 07 2019 at 11:41):

Someone could add a little custom tactic hook so unleak gets run between every line when you write begin [unleak] ... end

Andrew Ashworth (Jul 07 2019 at 12:06):

i haven't tried this but could you get around with pp.notation and custom notation? Is it possible to hack the pretty printer in any way?

Andrew Ashworth (Jul 07 2019 at 12:11):

hmm, i wonder if there is a vscode extension that watches tabs and can perform regex find and replace

Kevin Buzzard (Jul 07 2019 at 14:06):

I'm not sure I like this "duplicate every tactic" approach

How do we make Lean more user-friendly for beginners?

Kevin Buzzard (Jul 07 2019 at 14:07):

i haven't tried this but could you get around with pp.notation and custom notation? Is it possible to hack the pretty printer in any way?

The problem with this is that if the pretty printer is printing both mynat.zero and has_zero.zero as 0 then rw will still be failing and the error message will be incomprehensible.

Mario Carneiro (Jul 07 2019 at 14:32):

I would rather fix the original tactics rather than inventing new variations on existing things, which is more stuff for the newcomer to learn. If the set of tactics becomes too large, that itself will present a learning curve penalty

Tim Daly (Jul 07 2019 at 14:35):

Is there a "stack exchange" list somewhere that demonstrates each tactic? As a beginner it would be useful to see how and why to apply each tactic with a working example.

Kevin Buzzard (Jul 07 2019 at 14:57):

Here's something I knocked up very quickly last year, which covers some of the basics.

http://wwwf.imperial.ac.uk/~buzzard/xena/html/source/tactics/tacticindex.html

This summer I fully intend to make something a lot more professional.

For the more advanced tactics,

https://github.com/leanprover-community/mathlib/blob/master/docs/tactics.md

is invaluable.

Kevin Buzzard (Jul 07 2019 at 15:01):

I would rather fix the original tactics rather than inventing new variations on existing things, which is more stuff for the newcomer to learn. If the set of tactics becomes too large, that itself will present a learning curve penalty

For this notational leakage issue I am making a standalone thing; ultimately I would like induction to actually run induction'. Fixing this sort of issue in general is a different question. I like the idea of a custom recursor written with the canonical notation which gets tagged somehow and then picked up by the tactic in general.

So while I'm here, how can I actually override what happens when a user types induction in tactic mode? Editing core lean is fine. But the problem is that just changing the name of the induction tactic in core Lean will break everything, I would imagine. I'd rather have some other namespace open and with a higher priority.

Mario Carneiro (Jul 07 2019 at 15:07):

sadly lean interactive tactics don't work like that

Mario Carneiro (Jul 07 2019 at 15:08):

for it to get picked up you have to call it tactic.interactive.induction, and there can only be one of that

Kevin Buzzard (Jul 07 2019 at 15:13):

There are all sorts of ways I can work around this, e.g. processing user input before feeding it to Lean.

Johan Commelin (Jul 07 2019 at 15:32):

Can't one of @Keeley Hoek's

begin [ninja-mode]
  induction n,
end

make this work?

Mario Carneiro (Jul 07 2019 at 15:32):

yes, but that requires rewriting every tactic

Rob Lewis (Jul 07 2019 at 16:27):

It should actually be easy to define an alias newtac for tactic and automatically copy every declaration in tactic.interactive to newtac.interactive. Skip the ones you want to change, then use begin [newtac] ... end blocks. Not the prettiest workaround though.

Mario Carneiro (Jul 07 2019 at 16:29):

It would be great if there was an option or command line flag to set the default environment

Johannes Hölzl (Jul 07 2019 at 19:49):

I would vote for a per-module option (e.g. extend theory, so we can write noncomputable mathlib theory or mathlib theory)

Keeley Hoek (Jul 08 2019 at 10:00):

@Mario Carneiro Using my addition to lean 3.5.0 you can at least set the default monad for an interactive block.

Keeley Hoek (Jul 08 2019 at 10:01):

You create a new executor instance with priority higher than the default.

Kevin Buzzard (Jul 08 2019 at 10:28):

I am completely open to using 3.5.x with this project. It would be ideal if we could use induction a with d hd rather than induction'. My vision is that the user can only edit the stuff in a textbox within some begin end block (and can't see all the jibberish outside the block).


Last updated: Dec 20 2023 at 11:08 UTC