【发布时间】:2020-11-06 00:55:19
【问题描述】:
在我之前的question 中,我发现标准库(Julia v1.5)宏@deprecate 用于替换其他函数。
我想制作一个宏mark_deprecated,应用于函数时具有以下效果:
- 在调用目标函数时打印可自定义的弃用警告(如果可能,仅在第一次调用时)。
- 修改函数的文档(可查看为
julia>? function_name)以包含弃用警告。
当然,以后可能会包含许多其他方便的选项,例如指定替换函数的能力,产生错误而不是警告的选项等。
我主要将其作为 Julia 元编程的练习,到目前为止我的经验为零(有点担心这作为第一项任务可能太难了)。
试图理解@deprecate
作为第一步,我查看了当前的标准库 @deprecate 宏。如下:
# 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的事情在documentation 中有解释。 - 宏内部的变量
oldname和newname从不使用。我认为这是由于开发人员的疏忽造成的(与声明相反,尽管没有使用变量,但声明具有一些不明显的效果)。我删除它们。 - 我不确定如何处理
a(...) where B表达式(这样的表达式进入顶层elseif 块)。暂时不用担心那部分。似乎where表达式无论如何都被简单地剥离了。与表达式中的:curly括号相同。在任何情况下,函数符号 (oldsym) 似乎都是从表达式(第一个参数)中提取的。 - 我不明白
Base.show_unquoted究竟做了什么。似乎它将表达式“打印”到字符串中只是为了输出,所以我不会担心细节。 - 宏的主要内容当然是返回的
Expr。它断言它是在顶层进行评估的。我不关心的出口。 - 我不知道
Core.Typeof($(esc(oldsym))).name.mt.name是什么。它似乎是函数的实际Symbol(而不是包含符号的字符串)。Core.Typeof似乎与typeof相同。您可以使用typeof(some_function).name.mt.name并从mt::Core.MethodTable中取出符号。有趣的是,Tab-Completion 似乎不适用于这些低级数据结构及其字段。
走向我的宏
试图抄袭以上内容:
# 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
用于测试:
module Testing
import ..MarkDeprecated # (if in the same file)
a(x) = "Old behavior"
MarkDeprecated.@mark_deprecated a "Message" print
a("New behavior?")
end
问题
到目前为止,我没有做我想做的两件事:
- 当调用者不导入我用来连接文档字符串的
Markdown时,我该如何处理? (编辑:显然这不是问题?出于某种原因,尽管模块Markdown没有被导入到Testing模块中,但修改似乎有效。我不完全理解为什么。很难理解每个部分的位置宏生成的代码被执行...) - 实际上如何避免替换该函数?从内部调用它会创建一个无限循环。我基本上需要一个 Python 风格的装饰器?也许这样做的方法是只允许将
@mark_deprecated添加到实际的函数定义中? (这样的宏实际上是我期望在标准库中找到的,并且只是在我掉进这个兔子洞之前使用) - 宏(对于
@deprecate也是如此)不会影响我的示例中的方法a(x),因为它只创建一个带有签名a(args...)的方法,当宏调用一个参数时,该方法的优先级较低仅在函数符号上调用。虽然对我来说并不明显,但这似乎是@deprecate的理想行为。但是,是否可以将宏默认应用到裸函数符号以弃用 all 方法?
【问题讨论】:
标签: julia metaprogramming deprecated