求教两个问题


#1

我看完了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 worldruntime world是啥意思,请稍微解释下。我如果改写成这样的话,就可以运行了

julia> function tryeval()
           function newfun()
                  1
           end
           newfun()
           end

tryeval (generic function with 1 method)

julia> tryeval()
1

2

julia是具有编译时间和运行时间的。因此笼统的来说,对比python而言,Julia的速度快,是因为加上了 类型注释 才快的吗?


#2
  1. 这是#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。


#3
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

#4

新旧严格来说是指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


#5

你好,我现在的疑惑的地方就在于:

function tryval()
    @eval ceshi() = println("from tryval")
    ceshi()
end

function tryval()
    ceshi() = println("from tryval")
    ceshi()
end

这两种的区别,还有就是 那个 runtime age 和 current age 这个到底是指哪一部分的。这个的定义是什么,谢谢~


#6

弄清这两者的区别,可以阅读文档中scope和metaprogramming两章,并尝试解答这几个问题:

  1. 什么是global scope,什么是local scope, 什么是nested function?
  2. @eval ceshi() = println("from tryval")是在global scope中执行还是在local scope中执行?
  3. @eval ceshi() = println("from tryval")发生的时间是在compile time还是在runtime?

The “current world” is the age of the code that is currently running .

X-ref: https://discourse.julialang.org/t/help-calling-a-function-defined-from-expressions/1340/4


#7

我明白了,@eval是在global scope执行的。发生在parse time,谢谢了


#8

你好,我昨天想了想还是有点疑问。比如 current world 和 running world 对应上面代码是指哪一部分的。
还有
newfun =1
@eval newfun=1
newfun()

如果代码定义在global scope的话,上面的代码为何能执行成功?难道因为都是同一个world的吗


#9

你对上面的 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恶心久了觉得有必要强调一下。


#10

其实我明白你说的,但是我还是想搞清楚 跟 这个重定义方法 有点类似的几种代码 的 区别,防止出错。我现在记住了这个教训,少滥用eval,eval只能在global scope执行的。谢谢大佬了~