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
.
less(^, (String,Int64))
in the REPL to confirm, and this indeed took me to the same file and line. ^
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
.
×
into the terminal, I get cross (generic function with 1 method)
; less(×,(AbstractVector,AbstractVector))
, it takes me straight to a definition for cross
, not an explicit definition for ×
; 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 onnames(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?
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 grep
ping. 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...
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.
"""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
```
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With