Julia 制作宏以将函数标记为已弃用
在我之前的文章中,我发现标准库(Julia v1.5)宏Julia 制作宏以将函数标记为已弃用,julia,metaprogramming,deprecated,Julia,Metaprogramming,Deprecated,在我之前的文章中,我发现标准库(Julia v1.5)宏@deprecate用于替换其他函数 我想制作一个宏mark_deprecated,它在应用于函数时具有以下效果: 在调用目标函数时打印可自定义的弃用警告(如果可能,仅在第一次调用时) 修改函数的文档(可查看为julia>?function_name),以包括弃用警告 当然,以后可能会包括许多其他方便的选项,例如指定替换函数的功能、生成错误而不是警告的选项等 我主要是在Julia metaprogramming中做这项练习,目前为止我没有任
@deprecate
用于替换其他函数
我想制作一个宏mark_deprecated
,它在应用于函数时具有以下效果:
julia>?function_name
),以包括弃用警告# julia v1.5
# quoted from deprecated.jl, included by Base.jl
macro deprecate(old, new, ex=true)
meta = Expr(:meta, :noinline)
if isa(old, Symbol)
oldname = Expr(:quote, old)
newname = Expr(:quote, new)
Expr(:toplevel,
ex ? Expr(:export, esc(old)) : nothing,
:(function $(esc(old))(args...)
$meta
depwarn($"`$old` is deprecated, use `$new` instead.", Core.Typeof($(esc(old))).name.mt.name)
$(esc(new))(args...)
end))
elseif isa(old, Expr) && (old.head === :call || old.head === :where)
remove_linenums!(new)
oldcall = sprint(show_unquoted, old)
newcall = sprint(show_unquoted, new)
# if old.head is a :where, step down one level to the :call to avoid code duplication below
callexpr = old.head === :call ? old : old.args[1]
if callexpr.head === :call
if isa(callexpr.args[1], Symbol)
oldsym = callexpr.args[1]::Symbol
elseif isa(callexpr.args[1], Expr) && callexpr.args[1].head === :curly
oldsym = callexpr.args[1].args[1]::Symbol
else
error("invalid usage of @deprecate")
end
else
error("invalid usage of @deprecate")
end
Expr(:toplevel,
ex ? Expr(:export, esc(oldsym)) : nothing,
:($(esc(old)) = begin
$meta
depwarn($"`$oldcall` is deprecated, use `$newcall` instead.", Core.Typeof($(esc(oldsym))).name.mt.name)
$(esc(new))
end))
else
error("invalid usage of @deprecate")
end
end
我试图理解这一点(如果您理解宏,则无需阅读):
的内容将在中解释:meta
- 宏中的变量
和oldname
从不使用。我假设这是由于开发人员的疏忽造成的(尽管没有使用变量,但声明具有一些不明显的效果)。我移除它们newname
- 我不知道如何处理
表达式(这样的表达式进入顶级elseif块)。现在不用担心那部分了。看起来像是a(…),其中B
中的
表达式被简单地剥离了。与表达式中的
:花括号相同。似乎在任何情况下,函数符号(oldsym)都是从表达式(第一个参数)中提取出来的
- 我不明白Base.show\u unquoted到底做了什么。似乎它只是为了输出而将表达式“打印”成字符串,所以我不必担心细节
- 宏的主要内容当然是返回的
。它声称它是在顶层进行评估的。我不关心的出口问题Expr
- 我不知道什么是
。它似乎是函数的实际符号(与包含符号的字符串相反)Core.Typeof($(esc(oldsym))).name.mt.name
似乎与Core.Typeof
相同。您可以执行Typeof
并从typeof(some_function).name.mt.name
中取出符号。有趣的是,对于这些低级数据结构及其字段,标签补全似乎不起作用mt::Core.MethodTable
# julia v1.5
module MarkDeprecated
using Markdown
import Base.show_unquoted, Base.remove_linenums!
"""
@mark_deprecated old msg
Mark method `old` as deprecated.
Print given `msg` on method call and prepend `msg` to the method's documentation.
MACRO IS UNFINISHED AND NOT WORKING!!!!!
"""
macro mark_deprecated(old, msg="Default deprecation warning.", new=:())
meta = Expr(:meta, :noinline)
if isa(old, Symbol)
# if called with only function symbol, e.g. f, declare method f(args...)
Expr(:toplevel,
:(
@doc( # This syntax is riddiculous, right?!?
"$(Markdown.MD($"`$old` is deprecated, use `$new` instead.",
@doc($(esc(old)))))",
function $(esc(old))(args...)
$meta
warn_deprecated($"`$old` is deprecated, use `$new` instead.",
Core.Typeof($(esc(old))).name.mt.name)
$(esc(new))(args...)
end
)
)
)
elseif isa(old, Expr) && (old.head === :call || old.head === :where)
# if called with a "call", e.g. f(a::Int), or with where, e.g. f(a:A) where A <: Int,
# try to redeclare that method
error("not implemented yet.")
remove_linenums!(new)
# if old.head is a :where, step down one level to the :call to avoid code duplication below
callexpr = old.head === :call ? old : old.args[1]
if callexpr.head === :call
if isa(callexpr.args[1], Symbol)
oldsym = callexpr.args[1]::Symbol
elseif isa(callexpr.args[1], Expr) && callexpr.args[1].head === :curly
oldsym = callexpr.args[1].args[1]::Symbol
else
error("invalid usage of @mark_deprecated")
end
else
error("invalid usage of @mark_deprecated")
end
Expr(:toplevel,
:($(esc(old)) = begin
$meta
warn_deprecated($"`$oldcall` is deprecated, use `$newcall` instead.",
Core.Typeof($(esc(oldsym))).name.mt.name)
$(esc(old)) # TODO: this replaces the deprecated function!!!
end))
else
error("invalid usage of @mark_deprecated")
end
end
function warn_deprecated(msg, funcsym)
@warn """
Warning! Using deprecated symbol $funcsym.
$msg
"""
end
end # Module MarkDeprecated
问题
到目前为止,我没有做到我想要的两件事中的任何一件:
标记
,我如何处理这种情况,我用它连接docstring?(编辑:显然这不是问题?出于某种原因,尽管模块标记未导入到测试模块中,但修改似乎仍然有效。但我不完全理解原因。很难了解宏生成代码的每个部分在何处执行…)
@mark\u deprecated
添加到实际的函数定义中?(这样一个宏实际上是我在标准库中期望找到的,并且在我掉进这个兔子洞之前使用)@deprecate
)不会影响方法a(x)
,因为它只创建一个带有签名a(args…
)的方法,当仅对函数符号调用宏时,该方法对一个参数调用的优先级较低。虽然对我来说并不明显,但这似乎是@deprecate
想要的行为。但是,是否可以将宏对裸函数符号的默认应用程序设置为弃用所有方法我认为您想要实现的目标与
Base@deprecate
的目标不同。如果我理解正确:
- 您不希望宏为不推荐使用的方法创建定义;您更希望为手写定义添加注释
- 您想要修改docstring,而
不需要修改@deprecate
Base.@deprecate
如何工作并尝试调整它
关于你的具体问题:
1。当调用者不导入降价时,我如何处理这种情况?
下面的示例可能有助于解释事物是如何工作的:
module MyModule
# Markdown.MD, Markdown.Paragraph and msg are only available from this module
import Markdown
msg(name) = "Hello $name"
macro greet(name)
quote
# function names (e.g. Markdown.MD or msg) are interpolated
# => evaluated at macro expansion time in the scope of the macro itself
# => refer to functions available from within the module
$(Markdown.MD)($(Markdown.Paragraph)($msg($name)))
# (But these functions are not called at macro expansion time)
end
end
end
具体请参见msg
如何正确引用Main.MyModule.msg
,这是您必须从“外部”上下文调用它的方式:
2。也许这样做的方法是只允许将@mark\u不推荐添加到实际函数定义中?
是的,这就是我要做的
3。是否可以将宏对裸函数符号的默认应用程序设置为不推荐所有方法?
我想从技术上讲,不推荐给定函数的所有方法是可能的。。。或者至少是在弃用代码运行时存在的所有方法。但是,之后将定义的方法呢?
我个人不会这样做,只标记方法定义
也许类似的东西可以是一个存根,用作更复杂宏的起点,以精确地执行您想要的操作:
module MarkDeprecate
using Markdown
using MacroTools
function mark_docstring(docstring, message)
push!(docstring,
Markdown.Paragraph("Warning: this method is deprecated! $message"))
docstring
end
function warn_if_necessary(message)
@warn "This method is deprecated! $message"
end
macro mark_deprecate(msg, expr)
fundef = splitdef(expr)
prototype = :($(fundef[:name])($(fundef[:args]...);
$(fundef[:kwargs]...)) where {$(fundef[:whereparams]...)})
fundef[:body] = quote
$warn_if_necessary($msg)
$(fundef[:body])
end
quote
Base.@__doc__ $(esc(MacroTools.combinedef(fundef)))
Base.@doc $mark_docstring(@doc($prototype), $msg) $prototype
end
end
end
julia> @macroexpand MyModule.@greet "John"
quote
#= REPL[8]:8 =#
(Markdown.MD)((Markdown.Paragraph)((Main.MyModule.msg)("John")))
end
julia> MyModule.@greet "John"
Hello John
module MarkDeprecate
using Markdown
using MacroTools
function mark_docstring(docstring, message)
push!(docstring,
Markdown.Paragraph("Warning: this method is deprecated! $message"))
docstring
end
function warn_if_necessary(message)
@warn "This method is deprecated! $message"
end
macro mark_deprecate(msg, expr)
fundef = splitdef(expr)
prototype = :($(fundef[:name])($(fundef[:args]...);
$(fundef[:kwargs]...)) where {$(fundef[:whereparams]...)})
fundef[:body] = quote
$warn_if_necessary($msg)
$(fundef[:body])
end
quote
Base.@__doc__ $(esc(MacroTools.combinedef(fundef)))
Base.@doc $mark_docstring(@doc($prototype), $msg) $prototype
end
end
end
julia> """
bar(x::Number)
some help
"""
MarkDeprecate.@mark_deprecate "Use foo instead" function bar(x::Number)
42
end
bar
julia> """
bar(s::String)
This one is not deprecated
"""
bar(s::String) = "not deprecated"
bar
julia> methods(bar)
# 2 methods for generic function "bar":
[1] bar(s::String) in Main at REPL[4]:6
[2] bar(x::Number) in Main at REPL[1]:23
julia> @doc(bar)
bar(x::Number)
some help
Warning: this method is deprecated! Use foo instead
bar(s::String)
This one is not deprecated
julia> bar("hello")
"not deprecated"
julia> bar(5)
┌ Warning: This method is deprecated! Use foo instead
└ @ Main.MarkDeprecate REPL[1]:12
42