Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tactic/frozen): add tactics about frozen locals #7652

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/tactic/frozen.lean
@@ -0,0 +1,111 @@
/-
Copyright (c) 2021 Jannis Limperg. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jannis Limperg
-/

import tactic.dependencies

/-!
# Tactics About Frozen Local Instances

Under certain circumstances, Lean 'freezes' local constants which are instances.
This means that these local constants cannot be reverted: `revert` will throw an
error. The same applies to the dependencies of frozen local instances since
reverting a dependency would imply that we must revert the frozen local instance
as well. Example:

```lean
example {α} [i : has_add α] (x y : α) : x + y = y + x :=
begin
revert i, -- error: i is a frozen local instance
revert α, -- error: α is a dependency of i
end
```

Frozen local instances are good for performance: instances can be cached more
effectively if we know that they cannot be removed from the context.

This module provides various tactics related to frozen instances, e.g. to find
out whether a local constant is frozen or to unfreeze local instances if
necessary. We say that a local constant is frozen if it is either a frozen local
instance or a dependency of a frozen local instance.

Related core tactics: `tactic.frozen_local_instances`,
`tactic.freeze_local_instances`, `tactic.unfreeze_local_instances`,
`tactic.revertible_context`.
-/

open native
open name_set (local_list_to_name_set)

namespace tactic

/-- Returns true if local instances are frozen. Note that there may not
actually be instances in the local context, so the frozen status may not have
any effect. -/
meta def are_local_instances_frozen : tactic bool := do
(some _) ← frozen_local_instances | pure ff,
pure tt

/-- Returns the frozen local instances. If local instances are not frozen, or if
there are no local instances, the returned list is empty. Unlike
`tactic.frozen_local_instances`, this tactic never returns local constants
which do not appear in the context. -/
meta def frozen_local_instances' : tactic (list expr) := do
(some frozen) ← frozen_local_instances | pure [],
ctx ← local_context,
let ctx := local_list_to_name_set ctx,
pure $ frozen.filter (λ h, ctx.contains h.local_uniq_name)

/-- Returns the unique names of all frozen local constants. -/
meta def frozen_locals_name_set : tactic name_set := do
frozen ← frozen_local_instances',
deps ← dependency_name_sets_of_hyps_inclusive frozen,
pure $ deps.foldl name_set.union mk_name_set

/-- Returns all frozen local constants. The local constants are returned in no
particular order. -/
meta def frozen_locals : tactic (list expr) := do
frozen ← frozen_local_instances',
deps ← dependencies_of_hyps_inclusive frozen,
pure deps.join

/-- Returns all local constants that are *not* frozen. The locals are returned
in the order in which they appear in the context. This is a more precise (but
less efficient) version of `tactic.revertible_local_context`. -/
meta def non_frozen_locals : tactic (list expr) := do
frozen ← frozen_locals_name_set,
ctx ← local_context,
pure $ ctx.filter (λ h, ¬ frozen.contains h.local_uniq_name)

/-- `is_frozen h` returns true if the local constant `h` is frozen. -/
meta def is_frozen (h : expr) : tactic bool := do
frozen ← frozen_locals_name_set,
pure $ frozen.contains h.local_uniq_name

/-- `any_is_frozen_name_set hs` returns true if any of the local constants whose
unique names appear in `hs` are frozen. -/
meta def any_is_frozen_name_set (hs : name_set) : tactic bool := do
frozen ← frozen_locals_name_set,
pure $ frozen.fold ff (λ n b, b || hs.contains n)

/-- `any_is_frozen hs` returns true if any of the local constants `hs` are
frozen. -/
meta def any_is_frozen (hs : list expr) : tactic bool :=
any_is_frozen_name_set $ local_list_to_name_set hs

/-- `unfreeze_if_necessary_for_name_set hs` unfreezes local instances if any of
the local constants whose unique names appear in `hs` are frozen. -/
meta def unfreeze_if_necessary_for_name_set (hs : name_set) : tactic bool := do
tt ← any_is_frozen_name_set hs | pure ff,
unfreeze_local_instances,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfreeze should always be followed by freeze, since thawed instances are slow. Please check out and use the unfreezing combinator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, there's also unfreezing_hyp which is similar to this function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I found the unfreezing combinator after I had written all this stuff. My eventual use case is to make sure that certain reverts aren't blocked by frozen instances. Is it even worth checking whether the revert would be blocked (which can be somewhat expensive), versus just unfreezing (revert_lst ...) without the check?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Freeze and unfreeze are fairly cheap. Freeze requires lean to check for every hypothesis whether it is a type class or not, but that's it. There should be no performance impact (after the freeze) if the instances do not change.

The main performance issue is staying in the unfrozen state, because this disables the TC cache.

pure tt

/-- `unfreeze_if_necessary_for hs` unfreezes local instances if any of the local
constants in `hs` are frozen. You can use this before `revert_lst hs` to make
sure that the `revert` will not fail due to frozen local instances. -/
meta def unfreeze_if_necessary_for (hs : list expr) : tactic bool :=
unfreeze_if_necessary_for_name_set $ local_list_to_name_set hs

end tactic