在函数内部不能借助if定义函数的原理是什么


#1

测试代码如下

#!/usr/bin/env julia

function err(k)
    a=[1,2,3,4,5,6]
    if k==1
        f()=(a[1]=0)
    elseif k==2
        f()=(a[2]=0)
    else 
        f()=(a[3]=0)
    end
    f()
    println(string(a))
end

l=2
b=[1,2,3,4,5,6]
if l==1
    g()=(b[1]=0)
elseif l==2
    g()=(b[2]=0)
else 
    g()=(b[3]=0)
end
g()
println(string(b))

err(2)

将文件保存为err.jl。并在控制台输入
$julia -L err.jl
可以看到同样的一段代码,当位于函数体外时可以成功运行,而在函数体内时并没有定义出f(),以至于运行err(2)时出现UndefVarError。

请问这是什么原理?

环境:
Linux 4.9.0-4-amd64 Debian 4.9.65-3+deb9u1 (2017-12-23) x86_64 GNU/Linux
Version 1.1.0-DEV.232 (2018-09-10)
Commit 6caabc9d8b (316 days old master)


#3

看上去还是变量作用域的问题。

以下治标不治本,只是不报错了,但输出还是错的!

function err(k)
    a=[1,2,3,4,5,6]
    f() = nothing # 提前声明一下
    if k==1
        f()=(a[1]=0)
    elseif k==2
        f()=(a[2]=0)
    else 
        f()=(a[3]=0)
    end
    f()
    println("[k=$k] a=",string(a))
end

err(1)
err(3)
err(6)
julia>

b=[1, 0, 3, 4, 5, 6]
[k=1] a=[1, 2, 0, 4, 5, 6]
[k=3] a=[1, 2, 0, 4, 5, 6]
[k=6] a=[1, 2, 0, 4, 5, 6]
julia> 

https://docs.juliacn.com/latest/manual/variables-and-scoping/#全局作用域-1

突然发现事情并不简单,文档里说:

if 代码块是"有渗漏的",也就是说它们不会引入局部作用域。这意味着在 if 语句中新定义的变量依然可以在 if 代码块之后使用,尽管这些变量没有在 if 语句之前定义过。

可能是函数的定义有特殊性?

陷入沉思

function tst(x::Int)
   # f() = nothing  # 去掉注释仅仅是不报错,输出仍有问题
   if x == 1
      f() = 1
      println(1)
   elseif x == 0
      f() = 0
      println(0)
   elseif x == -1
      f() = -1
      println(1)
   else
      f() = 233
      println(233)
   end
   println("x=", x, "; f=", f())
end

保留注释的输出

julia> tst(1)
1
x=1; f=233

julia> tst(0)
0
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] tst(::Int64) at C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:64
 [2] top-level scope at none:0

julia> tst(-1)
1
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] tst(::Int64) at C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:64
 [2] top-level scope at none:0

去掉注释的输出

julia> tst(1)
1
x=1; f=233

julia> tst(0)
0
x=0; f=233

julia> tst(-1)
1
x=-1; f=233

看上去就是只保留了最后一次的函数定义,哪怕它不应该被执行过。


#4

新开一楼,记录下一些探索。

code_typed(tst, (Int,)) 这里 f()已经被换成 233 了。

julia> code_typed(tst, (Int,))
1-element Array{Any,1}:
 CodeInfo(
1 ─ %1  = (x === 1)::Bool
└──       goto #3 if not %1
2 ─       nothing::Const(#f#9(), false)
│         invoke Main.println(1::Int64)::Any
└──       goto #8
3 ─ %6  = (x === 0)::Bool
└──       goto #5 if not %6
4 ─       invoke Main.println(0::Int64)::Any
└──       goto #8
5 ─ %10 = (x === -1)::Bool
└──       goto #7 if not %10
6 ─       invoke Main.println(1::Int64)::Any
└──       goto #8
7 ─       invoke Main.println(233::Int64)::Any
8 ┄ %15 = φ (#2 => true, #4 => false, #6 => false, #7 => false)::Bool
│         nothing::MaybeUndef(Const(#f#9(), false))
│         $(Expr(:throw_undef_if_not, :f, :(%15)))::Any
│   %18 = invoke Main.println("x="::String, _2::Int64, "; f="::Vararg{Any,N} where N, 233)::Const(nothing, false)
└──       return %18
) => Nothing

code_llvm(tst, (Int,)) 生成的 llvm ir 钦定后两个分支报错了,还有不知道怎么多了一堆变量。

julia> code_llvm(tst, (Int,))

;  @ C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:51 within `tst'
; Function Attrs: uwtable
define void @julia_tst_13011(i64) #0 {
top:
  %1 = alloca %jl_value_t addrspace(10)*, i32 5
  %gcframe = alloca %jl_value_t addrspace(10)*, i32 3
  %2 = bitcast %jl_value_t addrspace(10)** %gcframe to i8*
  call void @llvm.memset.p0i8.i32(i8* %2, i8 0, i32 24, i32 0, i1 false)
  %3 = call %jl_value_t*** inttoptr (i64 1801331392 to %jl_value_t*** ()*)() #4
  %4 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0
  %5 = bitcast %jl_value_t addrspace(10)** %4 to i64*
  store i64 2, i64* %5
  %6 = getelementptr %jl_value_t**, %jl_value_t*** %3, i32 0
  %7 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %8 = bitcast %jl_value_t addrspace(10)** %7 to %jl_value_t***
  %9 = load %jl_value_t**, %jl_value_t*** %6
  store %jl_value_t** %9, %jl_value_t*** %8
  %10 = bitcast %jl_value_t*** %6 to %jl_value_t addrspace(10)***
  store %jl_value_t addrspace(10)** %gcframe, %jl_value_t addrspace(10)*** %10
  switch i64 %0, label %L14 [
    i64 1, label %L3
    i64 0, label %L8
    i64 -1, label %L12
  ]

L3:                                               ; preds = %top
;  @ C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:53 within `tst'
  call void @julia_println_13012(i64 1)
;  @ C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:64 within `tst'
  %11 = call %jl_value_t addrspace(10)* @jl_box_int64(i64 signext 1)
  %12 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 2
  store %jl_value_t addrspace(10)* %11, %jl_value_t addrspace(10)** %12
  %13 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 0
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 128616240 to
%jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %13
  %14 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 1
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 273937456 to
%jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %14
  %15 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 2
  store %jl_value_t addrspace(10)* %11, %jl_value_t addrspace(10)** %15
  %16 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 3
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 273937488 to
%jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %16
  %17 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 4
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 252889136 to
%jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %17
  %18 = call nonnull %jl_value_t addrspace(10)* @jl_invoke(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 274393232 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %1, i32 5)
  %19 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %20 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %19
  %21 = getelementptr %jl_value_t**, %jl_value_t*** %3, i32 0
  %22 = bitcast %jl_value_t*** %21 to %jl_value_t addrspace(10)**
  store %jl_value_t addrspace(10)* %20, %jl_value_t addrspace(10)** %22
  ret void

L8:                                               ; preds = %top
;  @ C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:56 within `tst'
  call void @julia_println_13012(i64 0)
  br label %err

L12:                                              ; preds = %top
;  @ C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:59 within `tst'
  call void @julia_println_13012(i64 1)
  br label %err

L14:                                              ; preds = %top
;  @ C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:62 within `tst'
  call void @julia_println_13012(i64 233)
  br label %err

err:                                              ; preds = %L8, %L12, %L14
;  @ C:\Users\woclass\Desktop\proj\Julia\JuliaCN\2087-funcINfunc.jl:64 within `tst'
  call void @jl_undefined_var_error(%jl_value_t addrspace(12)* addrspacecast (%jl_value_t* inttoptr (i64 252929848 to %jl_value_t*) to %jl_value_t addrspace(12)*))
  unreachable
}

#5

不支持这个是因为动态更行method table是很耗时的,函数在if里面并且名字一样意味着要不断渲染整个method table,得不偿失。所以直接不允许这么做。


#6

https://github.com/JuliaLang/julia/issues/15602,如果你非要弄的话,就要用匿名函数,就是g = ()->xxx