我想使用它的旧定义覆盖Julia中的一个函数。这样做的方法似乎是克隆功能并使用副本覆盖原始功能-类似于以下内容。但是,看来deepcopy(f)
只是返回了对f
的引用,所以这不起作用。
f(x) = x
f_old = deepcopy(f)
f(x) = 1 + f_old(x)
如何克隆功能?
背景:写一个宏@override
很有趣,它允许我逐点(甚至可能逐段)重写函数。
fib(n::Int) = fib(n-1) + fib(n-2)
@override fib(0) = 1
@override fib(1) = 1
该特定示例将很慢,并且可以使用@memoize
来提高效率。可能有充分的理由不这样做,但是在某些情况下,当定义一个功能时,可能还不完全了解该功能,因此有必要进行覆盖。
答案 0 :(得分:6)
我们可以使用IRTools.jl来做到这一点。
using IRTools
fib(n::Int) = fib(n-1) + fib(n-2)
const fib_ir = IRTools.code_ir(fib, Tuple{Int})
const fib_old = IRTools.func(fib_ir)
fib(n::Int) = n < 2 ? 1 : fib_old(fib, n)
julia> fib(10)
89
我们在这里所做的工作捕获了功能fib
的中间表示,然后将其重建为一个称为fib_old
的新功能。然后,我们可以根据fib
来覆盖fib_old
的定义!请注意,由于fib_old
被定义为递归调用fib
,而不是fib_old
,因此,当我们调用fib(10)
时不会出现堆栈溢出。
要注意的另一件事是,当我们调用fib_old
时,我们写的是fib_old(fib, n)
而不是fib_old(n)
。这是由于IRTools.func
的工作方式。
根据Slack上的Mike Innes:
在Julia IR中,所有函数都采用一个隐藏的额外参数来表示函数本身 原因是闭包是带有字段的结构,您需要在IR中访问
这是您的@override
宏的实现,语法略有不同:
function _get_type_sig(fdef)
d = splitdef(fdef)
types = []
for arg in d[:args]
if arg isa Symbol
push!(types, :Any)
elseif @capture(arg, x_::T_)
push!(types, T)
else
error("whoops!")
end
end
if isempty(d[:whereparams])
:(Tuple{$(types...)})
else
:((Tuple{$(types...)} where {$(d[:whereparams]...)}).body)
end
end
macro override(cond, fdef)
d = splitdef(fdef)
shadowf = gensym()
sig = _get_type_sig(fdef)
f = d[:name]
quote
const $shadowf = IRTools.func(IRTools.code_ir($(d[:name]), $sig))
function $f($(d[:args]...)) where {$(d[:whereparams]...)}
if $cond
$(d[:body])
else
$shadowf($f, $(d[:args]...))
end
end
end |> esc
end
现在可以键入
fib(n::Int) = fib(n-1) + fib(n-2)
@override n < 2 fib(n::Int) = 1
julia> fib(10)
89
最好的部分是,这几乎和我们将条件写入原始函数一样快(在运行时,而不是编译时!)
n = 15
fib2(n::Int) = n < 2 ? 1 : fib2(n-1) + fib2(n-2)
julia> @btime fib($(Ref(15))[])
4.239 μs (0 allocations: 0 bytes)
89
julia> @btime fib2($(Ref(15))[])
3.022 μs (0 allocations: 0 bytes)
89
答案 1 :(得分:1)
我真的不明白为什么要这么做(必须有更好的方法来获得想要的东西!)。
尽管如此,尽管不完全等效,但您可以通过使用匿名函数获得所需的内容:
julia> f = x->x
#3 (generic function with 1 method)
julia> f_old = deepcopy(f)
#3 (generic function with 1 method)
julia> f = x->1+f_old(x)
#5 (generic function with 1 method)
julia> f(4)
5