Zulip Chat Archive

Stream: Is there code for X?

Topic: use tactic


David Chanin (he) (S2'21) (Apr 18 2022 at 16:03):

I'm following along this gpt-f video, and at one point it has the proof:

example (n: ) :  (m: ), m > n :=
begin
  use n + 1,
  exact n.lt_succ_self,
end

However, when I try to write this in lean3, I get the error unknown identifier 'use'. What am I missing? Is there something I need to import?

Jireh Loreaux (Apr 18 2022 at 16:11):

If you have mathlib as a dependency to your project, import tactic at the beginning of the file should do the trick.

David Chanin (he) (S2'21) (Apr 18 2022 at 16:15):

You're a genius! That did it

David Chanin (he) (S2'21) (Apr 18 2022 at 16:17):

Is there a way to import just what I'm using? It makes me nervous to write things like import tactic since then I don't know where the labels I'm using in the code is from

David Chanin (he) (S2'21) (Apr 18 2022 at 16:17):

Or is there a way to see what's being imported when I write something like import tactic? I assume lots of stuff gets pulled in, but I can't tell how to see what it all is

Kevin Buzzard (Apr 18 2022 at 16:18):

import tactic imports a huge amount of stuff yes; it's what I encourage mathematicians to do because it gives them most basic math stuff and most tactics.

Kevin Buzzard (Apr 18 2022 at 16:20):

I think you can import tactic.interactive to get use working; this is an extremely minimal import which just gives you a bunch of really useful tactics which happen to be in mathlib rather than core Lean because they weren't needed in core Lean.

David Chanin (he) (S2'21) (Apr 18 2022 at 16:40):

interesting, so there's no equivalent to, ex, in python saying something like from tactic.interactive import use?

Julian Berman (Apr 18 2022 at 16:48):

The docs will tell you what import works as well -- https://leanprover-community.github.io/mathlib_docs/tactics.html#use (here it says indeed tactic.interactive, but also import tactic.basic)

Julian Berman (Apr 18 2022 at 16:49):

As for the Pythonism -- my perception is the Lean community, or at least mathlib, is less ... "cognizant" maybe? of such things. Or the style indeed is "import stuff and deal with it later if needed" seemingly.

Julian Berman (Apr 18 2022 at 16:49):

There's open foo (bar) which is sort of similar, it just pulls bar out of foo, but I don't know if there's similar syntax which works for import where a bunch of stuff may get plopped into your namespace immediately

Yaël Dillies (Apr 18 2022 at 16:56):

I believe such targeted imports wouldn't quite work in mathlib because maths is so underconnected. Further, there is a bunch of instance stuff that would unexpectedly break if we were not taking all transitive imports by default.

Julian Berman (Apr 18 2022 at 17:02):

So the Python import there still "imports" everything, it's just a question of what things you can refer to unqualified after it has run. So if importing something (in Lean) is like "have a bunch of side effects like registering some instances with the typeclass search system" then they'd still hypothetically run. I just mean that folks don't tend to like qualified imports, or minimizing plopping things in the global namespace, as much as in Python (which seems safe for a language like Lean but requires active energy coming from Python I think to not care about as much. To a Python programmer I'd say "use from imports as little as possible, and never import lots of stuff into the global namespace", and in Lean I just get the perspective that the reverse is generally preferred, folks will sometimes comment "why do you keep writing foo.something, get rid of the foo. with an open.)

Mario Carneiro (Apr 18 2022 at 19:32):

I rarely use open, the namespacing is pretty light in most parts of mathlib so it isn't needed, and certain namespaces like nat cause a lot of overloading if you open them

Mario Carneiro (Apr 18 2022 at 19:34):

I think the point is that python's from foo import bar is closest to lean's import foo open bar, and you can just leave off the last bit if you don't want to use it unqualified. In any case, for interactive tactics like use there is no namespacing at all: all tactics must be in the tactic.interactive namespace and they are all available unqualified in begin-end blocks if imported

Kyle Miller (Apr 18 2022 at 20:08):

A big difference between Python and Lean is how they handle the "linkage" problem, which is how do global variables end up actually referring to values?

The Python system: there are module objects, and a module contains a name->value mapping. When a module file is loaded it creates a module and (1) each module it imports gets added to this module's mapping (where from foo import x goes and copies the value for x in foo to this module), and (2) each function that gets defined gets a reference to the current module, since that way when that function is evaluated, it's able to evaluate global variables in the context of the module it was defined in. Modules double as a sort of hierarchical namespacing system, since when you import a module the module itself gets added to the current module's mapping, so foo.x turns into "look up foo in the current module, then ask foo for x."

The Lean system: there are two orthogonal systems, environments and modules. The environment is a name->value mapping, and each module you import extends the environment (and the name of the module may have no relation to the names it adds to the environment). There is a namespacing system from having hierarchical names. Then the open foo command just adds aliases to the environment for everything with the foo prefix, but any references to aliases get resolved into fully qualified names during elaboration time. There's an expectation that names will never be reused between different modules. This means that when a definition is evaluated, it can evaluate global variables by looking up a declaration by its fully qualified name alone, in any environment that contains that definition. This is useful when it comes to proving things because definitions can be unfolded, and any names you see are ones you can refer to yourself. I imagine this also makes passing things to the Lean kernel for final verification simpler.

The Lean system is sort of like Common Lisp's, where there are symbol packages (a complicated form of hierarchical names) and each symbol package contains some name->symbol mapping. Unlike Lean, each symbol directly contains the value it refers to, and the way the open equivalent works is that a reference to the symbol is added to another symbol package. Linkage happens at parse time, so when a function is evaluated it's able to evaluate global variables by just asking the symbol itself for its value.

In principle, Lean could allow you to do "tree shaking" and add only those declarations to the environment that are transitively necessary to work with a given declaration. But, in practice, since you're not supposed to reuse fully qualified names between different modules, I guess you don't need this sort of surgical namespace management.

Mario Carneiro (Apr 18 2022 at 20:15):

In principle, Lean could allow you to do "tree shaking" and add only those declarations to the environment that are transitively necessary to work with a given declaration. But, in practice, since you're not supposed to reuse fully qualified names between different modules, I guess you don't need this sort of surgical namespace management.

This would also be bad for stability, since it means that any change to the body of a definition would affect what definitions are available in downstream modules

Kyle Miller (Apr 18 2022 at 21:08):

Mario Carneiro said:

all tactics must be in the tactic.interactive namespace and they are all available unqualified in begin-end blocks if imported

I wanted to see if there might have been an edge case to this, but tactic blocks do seem to require the names of tactics to be in the tactic.interactive namespace -- aliases don't count.

meta def mytactics.foo := @tactic.interactive.simp

-- Create alias `tactic.interactive.foo` for `mytactics.foo`
open mytactics as tactic.interactive

-- This is ok
#print tactic.interactive.foo
-- meta def mytactics.foo  ...

-- Yet,
example : 1 = 1 :=
begin
  foo,
  -- unknown identifier 'foo'
end

Alex J. Best (Apr 18 2022 at 21:55):

I'm confused, this works fine, and the tactic isn't in the interactive namespace

namespace oof
meta def ayy : tactic unit :=
tactic.exact `(trivial)
end oof
open oof

example : true :=
begin
  ayy,
  tactic.trace_state
end

I thought there were some differences with tactics that took arguments, but not in general

Mario Carneiro (Apr 18 2022 at 22:22):

you need to put tactics in the interactive namespace if you want them to be parsed as interactive tactics, which means you get to use parse my_parser to parse arguments with custom syntax. You can also use any term in a begin end block as if it was a tactic, but in this case it is just treated as an expression so you have to conform to expression syntax and name resolution works as normal

Mario Carneiro (Apr 18 2022 at 22:24):

This affects even simple tactics with no arguments:

example : true :=
begin
  trivial; skip, -- ok
  ayy; skip, -- unknown identifier 'skip'
end

Last updated: Dec 20 2023 at 11:08 UTC