我看完了Julia的官方教程。其中有两个问题,没看明白。
1
方法
这一章中里面提到了重定义方法。教程说到:
当重定义一个方法或者增加一个方法时,知道这个变化不会立即生效很重要。这是Julia能够静态推断和编译代码使其运行很快而没有惯常的JIT技巧和额外开销的关键。实际上,任意新的方法定义不会对当前运行环境可见,包括Tasks和线程
这一点中的 任意新的方法定义不会对当前运行环境可见
这句话是什么意思。我有点看不懂,
julia> function tryeval()
@eval newfun() = 1
newfun()
end
tryeval (generic function with 1 method)
julia> tryeval()
ERROR: MethodError: no method matching newfun()
The applicable method may be too new: running in world age xxxx1, while current world is xxxx2.
Closest candidates are:
newfun() at none:1 (method too new to be called from this world context.)
in tryeval() at none:1
...
julia> newfun()
1
教程解释到:这个行为的实现通过一个「world age 计数器」。这个单调递增的值会跟踪每个方法定义操作。此计数器允许用单个数字描述「对于给定运行时环境可见的方法定义集」,或者说「world age」。它还允许仅仅通过其序数值来比较在两个 world 中可用的方法。在上例中,我们看到(方法 newfun 所存在的)「current world」比局部于任务的「runtime world」大一,后者在 tryeval 开始执行时是固定的。
这里面的current world
和 runtime world
是啥意思,请稍微解释下。我如果改写成这样的话,就可以运行了
julia> function tryeval()
function newfun()
1
end
newfun()
end
tryeval (generic function with 1 method)
julia> tryeval()
1
2
julia是具有编译时间和运行时间的。因此笼统的来说,对比python而言,Julia的速度快,是因为加上了 类型注释 才快的吗?
- 这是#265, v0.6之前,如果一个函数重定义了,调用这个函数的其它函数并不会自动更新,这个world age机制是为了解决这个问题。这里,函数
tryeval
做了两件事,1.用eval
修改了global中newfun
的定义;2. 调用newfun
,所以必须指明tryeval
调用的是才修改的newfun
还是老的newfun
, 直接这么写调用的是老的,下面这样写调用的是新的。
julia> function tryeval()
@eval newfun() = 1
Base.invokelatest(newfun)
end
笼统不好回答,编译执行和解释执行的差别,另外,python也有JIT。
1 个赞
function tryeval()
@eval newfun() = 1
Base.invokelatest(newfun)
end
我还是不太懂。newfun是在tryval里面第一次定义的,按理说只有这么一个newfun函数。因此也没有什么新旧之分。这该如何解释啊。
还有
我自己根据你的解释写了一段代码。你看下,
function ceshi()
println("from outer")
end
function tryval()
@eval ceshi() = println("from tryval")
Base.invokelatest(ceshi())
end
tryval()
julia> include(raw"d:\Code\julia\ceshi2.jl")
from outer
ERROR: LoadError: MethodError: objects of type Nothing are not callable
这个错误是怎么回事。
还有这种情况下,如下所示。这个时候为啥会自动调用最新的。
function ceshi()
println("from outer")
end
function tryval()
ceshi() = println("from tryval")
ceshi()
end
tryval()
julia> include(raw"d:\Code\julia\ceshi2.jl")
from tryval
新旧严格来说是指world的新旧,旧world的状态是“newfun
这个函数没定义”,错误也是这么说的。
function tryeval()
function newfun()
1
end
newfun()
end
和
function tryval()
ceshi() = println("from tryval")
ceshi()
end
都是在tryval
的local scope里定义了一个新函数,然后调用,与global是隔绝的,所以没问题。不通过closure,其它global scope中的函数也无法访问到在tryval
的local scope里的函数,不需要world age机制来处理。
Base.invokelatest(ceshi())
接口用错了,请参考文档:
help?> Base.invokelatest
invokelatest(f, args...; kwargs...)
Calls f(args...; kwargs...), but guarantees that the most recent method of f will be executed.
This is useful in specialized circumstances, e.g. long-running event loops or callback functions
that may call obsolete versions of a function f. (The drawback is that invokelatest is somewhat
slower than calling f directly, and the type of the result cannot be inferred by the compiler.)
由于 ceshi()
会返回 println
的返回值,也就是Nothing
,而Base.invokelatest
的第一个参数要求是可调用的,所以错误会说 Nothing are not callable
。
你好,我现在的疑惑的地方就在于:
function tryval()
@eval ceshi() = println("from tryval")
ceshi()
end
function tryval()
ceshi() = println("from tryval")
ceshi()
end
这两种的区别,还有就是 那个 runtime age 和 current age 这个到底是指哪一部分的。这个的定义是什么,谢谢~
弄清这两者的区别,可以阅读文档中scope和metaprogramming两章,并尝试解答这几个问题:
- 什么是global scope,什么是local scope, 什么是nested function?
@eval ceshi() = println("from tryval")
是在global scope中执行还是在local scope中执行?
@eval ceshi() = println("from tryval")
发生的时间是在compile time还是在runtime?
The “current world” is the age of the code that is currently running .
X-ref: Help calling a function defined from expressions - #4 by stevengj - Offtopic - Julia Programming Language
我明白了,@eval是在global scope执行的。发生在parse time,谢谢了
你好,我昨天想了想还是有点疑问。比如 current world 和 running world 对应上面代码是指哪一部分的。
还有
newfun =1
@eval newfun=1
newfun()
如果代码定义在global scope的话,上面的代码为何能执行成功?难道因为都是同一个world的吗
你对上面的 cross ref 里 Steven Johnson 的解释哪点不明白?
这个机制是为了解决代码更新时自动recompile的问题,world就是现实世界,current world就是现在(代码现在的状态,它可能被修改了),runtime world就是代码中某个函数运行时的那个时间点(这个时候代码也有一个状态,此时代码的状态可能即将被修改,通过 eval
)。
@eval
的宏展开时在parse time,但我想问的不是这个,而是eval
发生的时间,它是在 runtime 发生的。结合上面说的,现实世界中的一份代码,在某个函数的运行时被 eval
修改了,这份代码立即被分出了新旧两个状态,若所做的修改会影响此函数自身,则默认不会立即进行自我更新,需要等下一次调用的时候才会更新。若需要立即将代码自我更新,则用invoke latest。 此函数执行结束之后,所有其它受影响的函数都会自动更新(recompile)到最新的代码。
如果你用过Julia v0.5-,绝对会被这个问题恶心过。在不重启Julia的情况下,修改了一个函数,必须重新执行所有依赖此函数的函数定义,手动recompile。自动recompile是一个最自然的expected behavior,它已经被修复了。而正常写代码很少会遇到world age问题,遇到了也基本是滥用eval
导致的,我不知道为什么Julia官方教程会写这个东西,我不认为一个困扰Julia社区几年的问题是一个初学者有必要关心的,#265确实是一个很有名的issue,可能写教程的人被#265恶心久了觉得有必要强调一下。
其实我明白你说的,但是我还是想搞清楚 跟 这个重定义方法 有点类似的几种代码 的 区别,防止出错。我现在记住了这个教训,少滥用eval,eval只能在global scope执行的。谢谢大佬了~