如何在 julia 中找到现有函数 "aliases"?

How can I find existing function "aliases" in julia?

将事情放在上下文中:

a) 在 julia 中,可以通过 repeat("foo",n).

重复字符串 "foo" n

我试图找出是否为此定义了一个 "symbolic operator" 别名("alias" 在此处用于更广泛的意义),因为我怀疑可能会有,我发现了通过检查定义了 repeat(::String,::Integer) (types.jl) 的文件,它是 "foo" ^ n^ 在下面明确定义,它基本上是 repeat.

的包装器

b) ×cross 的别名,即调用 [1.,2.,3.] × [1.,2.,3.]cross([1.,2.,3.],[1.,2.,3.]) 似乎是等价的,而 is(×,cross) returns true.


Q1: Is there an easier programmatic way of checking if a function has an alias defined? i.e. is there an equivalent concept to:

julia> findaliases(cross)
  result: [×]

(I tried matching to :cross using a conditional list comprehension on names(Base), but without much luck)

Q2: Is there a programmatic way of doing the same for repeat(::String,::Int64) resulting in (^)(::AbstractString, ::Integer), given that technically it's not an alias, but a wrapper?, i.e.

julia> findwrappers(repeat,(String,Int64))
  result: [(^,(AbstractString, Integer))]

Or, in general, is there a convention on where / how such wrappers are defined that might help me to figure it out?

第一个问题,请尝试

julia> [x for x in names(Base) if eval(Base, x) === cross]
2-element Array{Symbol,1}:
 :cross
 :×

第二个,我不知道是否有比 grepping 更好的方法。我想,有 "brute force":

julia> [x for x in names(Base.Operators) if try eval(:(Base.Operators.$x("x", 3))) end == "xxx"]
1-element Array{Symbol,1}:
 :^

...我真的不推荐。但如果有效...

关于第二个: 寻找包裹其他东西的东西。 以下是最终解决方案的更多解决方案的开始, 因为很难确定程序员的意图。 特别是因为一个函数包装另一个函数意味着什么的概念并没有明确定义。 还是从这个解决方案,我认为你可以合理地调整它以适应你自己对一个函数包装另一个函数意味着什么的定义。

我相信 wraps 函数的作用与您的 findwrappers 相反, 但总的来说,你可以粗略地反转它,通过将它应用于所有加载模块中的 everything (实际上不会花费太长时间,之前做过类似的 - Base 最多可能需要 30 分钟)

目前的方法是召回率高于精度。 也就是说它宁愿return一个误报, 比错过一个。


方法

"""AST at this level often uses GlobalRefs -- define a way to represent them"""
Symbol(fgr::GlobalRef) = Symbol(module_name(fgr.mod),".", fgr.name)

"""
Returns a Nullable{tuple}. It is null if we do not wrap anything, or if we do then:
The first is a Symbol representing out best guess at what we are wrapping's name
The second is the whole last line, so you can work out for yourself what we are wrapping.
"""
function wraps(m::Method)
    ast = Base.uncompressed_ast(m)

    if ast[1]==nothing && length(ast)==2
        rline = ast[2]
        if rline.head == :return
            if rline.args[1].head == :call

                cline = rline.args[1]
                function_called = cline.args[1]
                if (typeof(function_called)<:GlobalRef 
                    && function_called.mod==Core 
                    && function_called.name==Symbol("_apply")) #Then calling a Callable-nonfunction

                    return  Nullable((Symbol(cline.args[2]), cline))
                else #It is a function call
                    return  Nullable((Symbol(function_called), cline))
                end
            end
        end

    end
    Nullable{Tuple{Symbol, Expr}}()
end

wraps(ff, arg_types) = wraps(first(methods(ff, arg_types)))

"Convience method, to produce nice simple list"
function wraps(ff)
    rets = Dict{String, String}()
    for m in methods(ff)
        wrapped = wraps(m)
        if !isnull(wrapped)
            me = string(m)
            them = string(first(get(wrapped)))
            rets[me] = them
        end
    end
    return rets
end

```


使用示例

因为 wraps 本身有一个 wrapper 定义

julia>wraps(wraps)
Dict{String,String} with 1 entry:
  "wraps(ff, arg_types) at In[217]:33" => "Main.wraps"

接下来的几个示例使用 "ugly" 但信息丰富的输出。 显示我们对正在包装的内容的猜测,以及完整的行,以便您可以检查该猜测:

julia>wraps(^, (String, Int64))
Nullable{Tuple{Symbol,Expr}}((Symbol("Base.repeat"),:((Base.repeat)(_2,_3))))

还有一个,这次它包装的不是真正的函数。 Base.string

julia> wraps(*,(String, String))
Nullable{Tuple{Symbol,Expr}}((Symbol("Base.string"),:((Core._apply)(Base.string,(Core.tuple)(_2),_3))))

显示它没有找到不存在的包装器(因为 ^ 包装,repreat 不是相反)

julia> wraps(repeat, (String, Int64))
Nullable{Tuple{Symbol,Expr}}()

parse 的包装器之一,实际上非常通用。 _1 表示它的第一个参数 因为 parse(T,c::Char) 会退回到 T(c) 所以 parse 实际上(动态地)包装了它的第一个参数。 您可能不喜欢 wrap 的定义,因此您可能必须通过修改 wraps 函数来排除它。

julia>wraps(parse)

Dict{String,String} with 6 entries:
  "parse{T<:Integer}(::Type{T}, s::AbstractString, base::Integer) at parse.jl:150" => "Base.get"
  "parse{T<:Integer}(::Type{T}, c::Char) at parse.jl:6" => "_1"
  "parse{T<:Integer}(::Type{T}, s::AbstractString) at parse.jl:152" => "Base.get"
  "parse(stream::IO) at markdown/Julia/interp.jl:4" => "Markdown.#parse#84"
  "parse(str::AbstractString, pos::Int64) at parse.jl:179" => "Base.#parse#232"
  "parse(str::AbstractString) at parse.jl:194" => "Base.#parse#233"

所以现在是一个真正复杂的, 方法出错的地方,有时

julia> wraps(^)

Dict{String,String} with 26 entries:
  "^(x, p::Integer) at intfuncs.jl:144" => "Base.power_by_squaring"
  "^(x::BigInt, y::BigInt) at gmp.jl:434" => "GMP.bigint_pow"
  "^{T<:Integer}(z::Complex{T}, n::Integer) at complex.jl:572" => "Base.power_by_squaring"
  "^(::Irrational{:e}, x::Irrational) at irrationals.jl:181" => "Base.exp"
  "^(x::Irrational, y::Irrational) at irrationals.jl:95" => "Base.^"
  "^(s::AbstractString, r::Integer) at strings/types.jl:178" => "Base.repeat"
  "^(x::Bool, y::Bool) at bool.jl:51" => "Base.|"
  "^{T<:AbstractFloat}(x::T, y::Rational) at rational.jl:358" => "Base.^"
  "^(x::Number, y::Rational) at rational.jl:357" => "Base.^"
  "^(x::BigInt, y::Integer) at gmp.jl:436" => "GMP.bigint_pow"
  "^(x::Float32, y::Integer) at math.jl:318" => "Math.box"
  "^(x::Integer, y::BigInt) at gmp.jl:437" => "GMP.bigint_pow"
  "^(x::Number, p::Integer) at intfuncs.jl:143" => "Base.power_by_squaring"
  "^(::Irrational{:e}, x::Number) at irrationals.jl:181" => "Base.exp"
  "^(x::Float64, y::Integer) at math.jl:316" => "Math.box"
  "^{T<:Integer}(x::T, p::T) at intfuncs.jl:142" => "Base.power_by_squaring"
  "^(x::Float16, y::Integer) at float16.jl:154" => "Base.Float16"
  "^{T<:Number}(x::T, y::T) at promotion.jl:235" => "Base.no_op_err"
  "^(z::Complex, n::Integer) at complex.jl:565" => "Base.^"
  "^(::Irrational{:e}, x::Rational) at irrationals.jl:181" => "Base.exp"
  "^(x::Integer, y::Bool) at bool.jl:52" => "Base.ifelse"
  "^(a::Float16, b::Float16) at float16.jl:136" => "Base.Float16"
  "^(x::Number, y::Number) at promotion.jl:194" => "Base.^"
  "^{T<:AbstractFloat}(x::Complex{T}, y::Rational) at rational.jl:359" => "Base.^"
  "^(x::Bool, y::BigInt) at gmp.jl:438" => "(Core.getfield)(Base.GMP.Base,:power_by_squaring)"
  "^(::Irrational{:e}, x::Integer) at irrationals.jl:181" => "Base.exp"

因此,要深入了解声称是 Math.box 的包装器的错误所在,请使用丑陋但信息丰富的重载:

julia> wraps(^, (Float32, Integer))

Nullable{Tuple{Symbol,Expr}}((Symbol("Math.box"),:((Base.Math.box)(Base.Math.Float32,(Base.Math.powi_llvm)((Base.Math.unbox)(Base.Math.Float32,_2),(Base.Math.unbox)(Base.Math.Int32,(Base.Math.Int32)(_3)))))))

所以是的,在这种情况下,它看起来像是 Base.Math.powi_llvm 的别名。 您可以在 Wraps 方法中定义一个特殊情况以使其运行并挖掘 Math.box 调用以找到真实姓名。 就像我为 Core._apply

做的一样

结论

这是一个启发式的解决方案。 它的回想是我认为非常好的。 我怀疑知道包装器是否是包装器的精确度也很好,但是能够弄清楚它是包装器的用途的能力不是。

一般来说,有 1 行长的 AST(在 nothing 的明显强制性第一行之后)并且看起来像

Expr
  head: Symbol return
  args: Array{Any}((1,))
    1: Expr
      head: Symbol call

强烈建议该方法是包装器。 因为这个方法唯一做的就是调用其他函数。 确定该功能是什么是困难的部分。

您可能会注意到我只找到了函数的名称——没有找到它们的 arg 类型——所以没有找到方法。可以通过在 AST 中查找 _1_2 等来计算出 arg 类型——它们映射到进行包装的方法的参数。但这是 reader.

的练习