Zulip Chat Archive
Stream: lean4
Topic: Deriving Add
Tomas Skrivan (Oct 12 2022 at 19:14):
I would like to derive Add
for any structure that has elements equipped with Add
. My ultimate goal is to automatically derive that a structure is a vector space if all its elements form a vectors space. What is the best way to approach this?
Tomas Skrivan (Oct 12 2022 at 19:15):
My attempt at registering new deriving Add
but it does not seem to do anything
import Lean.Meta.Inductive
import Lean.Elab.Deriving.Basic
import Lean.Elab.Deriving.Util
namespace Lean.Elab.Deriving
open Command
open Lean.Parser.Term
open Meta
def mkAddHandler (declNames : Array Name) : CommandElabM Bool := do
if (← declNames.allM isInductive) && declNames.size > 0 then
IO.println "Calling Add Handler!"
return true
else
return false
builtin_initialize
registerDerivingHandler ``Add mkAddHandler
structure Float2 where
(x y : Float)
deriving Add
Mario Carneiro (Oct 12 2022 at 19:20):
you should use initialize
instead of builtin_initialize
, and you should put Float2
in another file
Tomas Skrivan (Oct 12 2022 at 19:27):
Ahh it is being registered correctly now! Thanks!
Tomas Skrivan (Oct 12 2022 at 20:23):
If anyone is interested here is the solution:
import Lean.Elab.Deriving.Basic
open Lean Elab Command
private def mkAddInstanceCmd (declName : Name) : TermElabM (TSyntax "command") := do
let env ← getEnv
match getStructureInfo? env declName with
| some info => do
let xName : Name := "x"
let yName : Name := "y"
let args : TSyntaxArray "term" ← info.fieldNames.mapM (λ name => `(($(mkIdent <| xName.append name) + $(mkIdent <| yName.append name))))
let addFun : TSyntax "term" ← `(λ $(mkIdent xName) $(mkIdent yName) => $(mkIdent (declName.append "mk")) $args*)
let cmd ← `(instance : Add $(mkIdent declName) := ⟨$addFun⟩)
return cmd
| none =>
pure default
def mkAddHandler (declNames : Array Name) : CommandElabM Bool := do
let env ← getEnv
if (← declNames.allM (λ name => pure (isStructure env name))) && declNames.size > 0 then
declNames.forM λ name => do
let cmd ← liftTermElabM <| mkAddInstanceCmd name
elabCommand cmd
return true
else
return false
initialize
registerDerivingHandler ``Add mkAddHandler
It works but I'm really not sure if I got the macro hygiene correct. Creating names and identifiers is still a bit of a mystery to me.
Mario Carneiro (Oct 12 2022 at 20:24):
macro hygiene is generally as simple as using identifiers directly in the syntax quotation
Mario Carneiro (Oct 12 2022 at 20:25):
what you are doing is manually creating unhygienic identifiers
Tomas Skrivan (Oct 12 2022 at 20:27):
That was my suspicion that the arguments x
and y
in the lambda are not hygienic. But I wasnt sure how can I create the array args
e.g. #[(x.x + y.x), (x.y + y.y)]
Tomas Skrivan (Oct 12 2022 at 20:30):
This works, but not sure if it is the intended way:
let x : TSyntax "ident" ← `(x)
let y : TSyntax "ident" ← `(y)
let args : TSyntaxArray "term" ← info.fieldNames.mapM (λ name => `(( $x.$(mkIdent name) + $y.$(mkIdent name))))
let addFun : TSyntax "term" ← `(λ $x $y => $(mkIdent (declName.append "mk")) $args*)
Mario Carneiro (Oct 12 2022 at 20:32):
private def mkAddInstanceCmd (declName : Name) : TermElabM (TSyntax "command") := do
let env ← getEnv
match getStructureInfo? env declName with
| some info => do
let mk := (getStructureCtor env declName).name
let args : TSyntaxArray `term ← info.fieldNames.mapM fun name =>
`((x.$(mkIdent name) + y.$(mkIdent name)))
let addFun : Term ← `(λ x y => $(mkIdent mk) $args*)
`(instance : Add $(mkIdent declName) := ⟨$addFun⟩)
| none =>
pure default
Tomas Skrivan (Oct 12 2022 at 20:34):
Interesting, I thought that this way the x
and y
would end up being different identifiers in args
and addFun
.
Mario Carneiro (Oct 12 2022 at 20:35):
the identifiers depend on the current "macro scope", which is a ReaderT argument so it can't change unless you use a combinator like withFreshMacroScope
Mario Carneiro (Oct 12 2022 at 20:36):
it gets incremented when you call back into the macro system or do one step of macro expansion
Mario Carneiro (Oct 12 2022 at 20:36):
and you can also use that function directly if you want lots of variables
Mario Carneiro (Oct 12 2022 at 20:38):
The (mkIdent mk) $args*
still isn't quite correct because the constructor could have implicit arguments. It's probably better to use a structure literal
Mario Carneiro (Oct 12 2022 at 20:42):
let fields : Array Ident := info.fieldNames.map mkIdent
let fields1 := fields; let fields2 := fields
`(instance : Add $(mkIdent declName) := ⟨λ x y => {
$[$fields:ident := x.$fields1 + y.$fields2]*
}⟩)
Mario Carneiro (Oct 12 2022 at 20:42):
cc: @Sebastian Ullrich any particular reason that you can't reuse variables in a syntax quotation?
Sebastian Ullrich (Oct 12 2022 at 20:50):
No particular reason, just an edge case in the implementation
Mario Carneiro (Oct 12 2022 at 20:54):
ooh, if you use $(id fields)
you get an even weirder error
Tomas Skrivan (Oct 12 2022 at 20:55):
I would like to generalize it to user specified binary operation, but for className := ``Add
and opName := ``Add.add
, this breaks:
let fields : Array Ident := info.fieldNames.map mkIdent
let fields1 := fields; let fields2 := fields
`(instance : $(mkIdent className) $(mkIdent declName) := ⟨λ x y => {
$[$fields:ident := $(mkIdent opName):ident x.$fields1 y.$fields2]*}⟩)
Mario Carneiro (Oct 12 2022 at 21:01):
:sad: I guess the implementation isn't that great at mixing variables with different repetitions. You can still do the original thing:
let className := ``Add
let opName := ``Add.add
let fields ← info.fieldNames.map mkIdent |>.mapM fun field =>
`(Lean.Parser.Term.structInstField|
$field:ident := $(mkIdent opName):ident x.$field y.$field)
`(instance : $(mkIdent className) $(mkIdent declName) := ⟨λ x y => { $fields* }⟩)
Tomas Skrivan (Oct 12 2022 at 21:34):
Unfortunately this totally fails for parameterized structures such as:
structure Pair2 (α : Type) [Add α] where
(x y : α)
deriving Add
Tomas Skrivan (Oct 12 2022 at 23:56):
Horray, I can do it even for parameterized structures! I could not figure it out by manipulating Syntax
so I had to get my hands dirty and manipulate Expr
directly and create Declaration
private def mkBinaryOpInstance (className opName : Name) (declName : Name) : TermElabM Unit := do
let env ← getEnv
match getStructureInfo? env declName with
| some info => do
IO.println s!"{info.structName}"
let structType ← Meta.inferType (mkConst info.structName)
let instValue ← Meta.forallTelescope structType λ xs b =>
do
let strct ← Meta.mkAppOptM info.structName (xs.map some)
let binOp ←
Meta.withLocalDecl `x default strct λ x =>
Meta.withLocalDecl `y default strct λ y =>
do
let mut fields := #[]
for i in [0:info.fieldNames.size] do
fields := fields.push (← Meta.mkAppM opName #[x.proj info.structName i, y.proj info.structName i])
(Meta.mkLambdaFVars #[x,y] (← Meta.mkAppM (info.structName.append "mk") fields))
Meta.mkLambdaFVars xs (← Meta.mkAppM (className.append "mk") #[binOp])
let instType ← Meta.inferType instValue
let instName : Name := s!"inst{className}{declName}"
let instDecl : Declaration := .defnDecl
{levelParams := [],
hints := .abbrev,
safety := .safe,
type := instType,
value := instValue,
name := instName,
}
addAndCompile instDecl
Attribute.add instName "instance" default
| none =>
pure default
def mkBinaryOpHandler (className opName : Name) (declNames : Array Name) : CommandElabM Bool := do
let env ← getEnv
if (← declNames.allM (λ name => pure (isStructure env name))) && declNames.size > 0 then
declNames.forM λ name => do
liftTermElabM <| mkBinaryOpInstance className opName name
return true
else
return false
initialize
registerDerivingHandler ``Add (mkBinaryOpHandler ``Add ``Add.add)
Last updated: Dec 20 2023 at 11:08 UTC