Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

alias

julia

To put things into context:

a) in julia, one can repeat string "foo" n times by doing repeat("foo",n).

I was trying to find out if there was a "symbolic operator" alias defined for this ("alias" used in the broader sense here), because I suspected there probably would be, and I found out it's "foo" ^ n by inspecting the file where repeat(::String,::Integer) is defined (types.jl); ^ is explicitly defined right below, and it's basically a wrapper to repeat.

  • with that knowledge, I typed less(^, (String,Int64)) in the REPL to confirm, and this indeed took me to the same file and line.
  • if I type ^ in the REPL, I get ^ (generic function with 47 methods)

b) × is an alias for cross, i.e. the calls [1.,2.,3.] × [1.,2.,3.] and cross([1.,2.,3.],[1.,2.,3.]) seem to be equivalent, and is(×,cross) returns true.

  • if I type × into the terminal, I get cross (generic function with 1 method);
  • if I type less(×,(AbstractVector,AbstractVector)), it takes me straight to a definition for cross, not an explicit definition for ×;
  • after a bit of "grepping" I found the definition const × = cross in sysimg.jl.

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?

like image 486
Tasos Papastylianou Avatar asked Sep 03 '16 00:09

Tasos Papastylianou


2 Answers

For your first question, try

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

For your second, I don't know if there's a better way than grepping. I suppose, there is "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}:
 :^

...which I don't really recommend. But if it works...

like image 82
Fengyang Wang Avatar answered Nov 07 '22 15:11

Fengyang Wang


With regards to the second: Finding what things wrap other things. The below is more of a start of a solution that a final solution, because determining programmer intent is hard. Particularly since the very notion of what it means for a function to wrap another is not clearly defined. Still from this solution I think you can reasonably tweak it to fit your own definition of what it means for a function to wrap another.

I believe the wraps function does the opposite of your findwrappers, but in general you can roughly invert it, by applying it to everything in all loaded modules (which actually doesn't take too long, having done similar before -- maybe 30 minutes tops for Base)

The method at the moment goes for Recall over Precision. That is to say it would rather return a false positive, than miss one.


Method

"""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

```


Examples of Use

Because wraps itself has a wrapper definition

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

The next few examples use the "ugly" but informative output. Showing both our guess at what is being wraps, and the full line so you can check that guess:

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

And another, This time it is wrapping something that isn't truely a function. Base.string

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

Showing that it doesn't find wrappers that are not there (since ^ wraps, repreat not the other way round)

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

One of parse's wrapper, is actually greally generic. the _1 means its first argument Because parse(T,c::Char) will fall back to T(c) So parse actually (dynamically) wraps it's first argument. You might not like that definitions of wrap so you might have to exclude it by modifying the wraps function.

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"

So now a real complex one, where the method gets it wrong, sometimes

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"

So to dig into what is going wrong with the one that claims to be a wrapper for Math.box, using the ugly but informative overload:

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)))))))

So yes, looks like it is, in this case, a alias for Base.Math.powi_llvm. You could define a special case in the Wraps method to make it go and dig through Math.box call's to find the real name. Like I did for Core._apply


Conclusion

So a heuristic solution. It's recall is I think very good. I suspect it precision at knowing if something a a wrapper or not is also very good, but ability work out what it is a wrapper for, is not.

In general, having a AST that is 1 line long (after the apparently manditory first line of nothing) and the looks like

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

is a strong suggestion that the method is a wrapper. Since the only thing this method does is call some other function. identifying what that function is, is the hard part.

You may note I only found the names of the functions -- not their arg types -- so not the methods. It is possible to work out the arg types, by looking for _1, _2 etc in the AST -- which map to the parameters of the method that is doing the wrapping. But this is left as an exercise to the reader.

like image 3
Lyndon White Avatar answered Nov 07 '22 16:11

Lyndon White