Adding StatsBase.jl weights methods to a function in Julia - julia

I am trying to add methods to a function similar to the example function my_function below. These methods should be dispatched when any of the subtypes of AbstractWeights from the StatsBase.jl package are passed.
I don't encounter any problem when writing the example function with Abstract and Primitive types from the Base package. E.g.
function my_function(v::Array{<:Real,1})
return sum(v .* 3)/length(v)
end
function my_function(v::Array{<:Real,1}, w::Array{<:Real,1})
return sum(v .* w .* 3)/sum(w)
end
v = [1,2,3]
w = [3,2,1]
my_function(v)
# 6.0
my_function(v, w)
# 5.0
However, when adding the methods for the types from StatsBase.jl I get MethodError errors:
using StatsBase
my_function(v::Array{<:Real,1}, w::Array{<:AbstractWeights,1}) = my_function(v,w)
my_function(v::Array{<:Real,1}, w::Array{Weights,1}) = my_function(v,w)
my_function(v, pweights(w))
# ERROR: LoadError: MethodError: no method matching my_function(::Vector{Int64}, ::ProbabilityWeights{Int64, Int64, Vector{Int64}})
my_function(v, weights(w))
# ERROR: LoadError: MethodError: no method matching my_function(::Vector{Int64}, ::Weights{Int64, Int64, Vector{Int64}})
How can I write the methods for the StatsBase.jl weights types above?
If the function worked well, the following should be true
my_function(v, w) == my_function(v, weights(w)) == my_function(v, pweights(w)) == my_function(v, fweights(w))
# true

There are several issues here:
AbstractWeights is already a vector, so you do not need to wrap it in a vector;
your implementation is recursive (the method calls itself).
So the way you should implement the code is:
my_function(v::Vector{<:Real}, w::AbstractWeights) = sum(v .* w .* 3)/sum(w)

Related

New dispatch for isless function does not work

I need to be able to compare instances of a struct named Hijri, so I have defined a new method for the isless() function as follows:
function isless(a::Hijri, b::Hijri)
tuple_a = datetuple(a)
tuple_b = datetuple(b)
return tuple_a < tuple_b
end
datetuple() returns values of the Hijri struct as a tuple.
This works fine:
#show isless(a, b)
However, this one
#show a < b
throws an error:
ERROR: LoadError: MethodError: no method matching isless(::Main.HijriConverter.Hijri, ::Main.HijriConverter.Hijri)
Closest candidates are:
isless(::Any, ::Missing) at ~/Downloads/julia-1.7.0/share/julia/base/missing.jl:88
isless(::Missing, ::Any) at ~/Downloads/julia-1.7.0/share/julia/base/missing.jl:87
Stacktrace:
[1] <(x::Main.HijriConverter.Hijri, y::Main.HijriConverter.Hijri)
# Base ./operators.jl:352
[2] top-level scope
# show.jl:1047
in expression starting at /home/jafar_isbarov/Documents/projects/hijri/HijriConverter.jl/src/HijriConverter.jl:1
What could be the reason?
I had to import isless from Base:
import Base: isless
or use Base.isless (instead of isless) in my function definition.
function Base.isless(...)
end
This question was originally answered on Julia Discourse.

Keyword Arguments - Functions

I am a beginner in Julia,
how can I create functions with keywords for arguments without having to initialize these arguments in function?
A very simple example:
function f(;a = 1, b = 2)
a+b
end
I would like to do:
function f(;a, b)
a+b
end
Best regards.
This is a new feature in version 0.7 — you can actually write it just as you'd like.
Julia's syntax on versions 0.6 and prior require you to give them a default value, but since that default value is evaluated at call time, you can actually use an error function to require them:
julia> function f(;a=error("a not provided"), b=error("b not provided"))
a+b
end
f (generic function with 1 method)
julia> f()
ERROR: a not provided
Stacktrace:
[1] f() at ./REPL[1]:2
julia> f(a=2)
ERROR: b not provided
Stacktrace:
[1] (::#kw##f)(::Array{Any,1}, ::#f) at ./<missing>:0
julia> f(a=2, b=3)
5
This is comming in Julia 0.7 line:
Keyword arguments can be required: if a default value is omitted, then an exception is thrown if the caller does not assign the keyword a value (#25830).
So:
function f(;a, b)
a+b
end
Will become syntax sugar for:
function f(;a = throw(UndefKeywordError(:a)), b = throw(UndefKeywordError(:b)))
a+b
end
Another workaround is to create a function with variadic keyword arguments and leave any requirements over the expected keyword inputs as assertions inside the code. E.g.
function f( ; kwargs... )
V = Dict( kwargs )
try; assert( haskey( V, :a ) ); assert( haskey( V, :b ) )
catch e; throw( AssertionError("KWargs need to be a and b") )
end
V[:a] + V[:b]
end
f(a=1, b=2) #> 3
f(a=1, c=2) #> ERROR: AssertionError: KWargs need to be a and b
Or even as simple as:
function f( ; kwargs... )
V = Dict( kwargs )
a = V[:a]
b = V[:b]
a + b
end
f(a=1, c=2) #> ERROR: KeyError: key :b not found
Disclaimer: I'm not recommending this, I'm just saying it's another workaround to consider depending on what functionality you have in mind.

Evaluate expression with local variables

I'm writing a genetic program in order to test the fitness of randomly generated expressions. Shown here is the function to generate the expression as well a the main function. DIV and GT are defined elsewhere in the code:
function create_single_full_tree(depth, fs, ts)
"""
Creates a single AST with full depth
Inputs
depth Current depth of tree. Initially called from main() with max depth
fs Function Set - Array of allowed functions
ts Terminal Set - Array of allowed terminal values
Output
Full AST of typeof()==Expr
"""
# If we are at the bottom
if depth == 1
# End of tree, return function with two terminal nodes
return Expr(:call, fs[rand(1:length(fs))], ts[rand(1:length(ts))], ts[rand(1:length(ts))])
else
# Not end of expression, recurively go back through and create functions for each new node
return Expr(:call, fs[rand(1:length(fs))], create_single_full_tree(depth-1, fs, ts), create_single_full_tree(depth-1, fs, ts))
end
end
function main()
"""
Main function
"""
# Define functional and terminal sets
fs = [:+, :-, :DIV, :GT]
ts = [:x, :v, -1]
# Create the tree
ast = create_single_full_tree(4, fs, ts)
#println(typeof(ast))
#println(ast)
#println(dump(ast))
x = 1
v = 1
eval(ast) # Error out unless x and v are globals
end
main()
I am generating a random expression based on certain allowed functions and variables. As seen in the code, the expression can only have symbols x and v, as well as the value -1. I will need to test the expression with a variety of x and v values; here I am just using x=1 and v=1 to test the code.
The expression is being returned correctly, however, eval() can only be used with global variables, so it will error out when run unless I declare x and v to be global (ERROR: LoadError: UndefVarError: x not defined). I would like to avoid globals if possible. Is there a better way to generate and evaluate these generated expressions with locally defined variables?
Here is an example for generating an (anonymous) function. The result of eval can be called as a function and your variable can be passed as parameters:
myfun = eval(Expr(:->,:x, Expr(:block, Expr(:call,:*,3,:x) )))
myfun(14)
# returns 42
The dump function is very useful to inspect the expression that the parsers has created. For two input arguments you would use a tuple for example as args[1]:
julia> dump(parse("(x,y) -> 3x + y"))
Expr
head: Symbol ->
args: Array{Any}((2,))
1: Expr
head: Symbol tuple
args: Array{Any}((2,))
1: Symbol x
2: Symbol y
typ: Any
2: Expr
[...]
Does this help?
In the Metaprogramming part of the Julia documentation, there is a sentence under the eval() and effects section which says
Every module has its own eval() function that evaluates expressions in its global scope.
Similarly, the REPL help ?eval will give you, on Julia 0.6.2, the following help:
Evaluate an expression in the given module and return the result. Every Module (except those defined with baremodule) has its own 1-argument definition of eval, which evaluates expressions in that module.
I assume, you are working in the Main module in your example. That's why you need to have the globals defined there. For your problem, you can use macros and interpolate the values of x and y directly inside the macro.
A minimal working example would be:
macro eval_line(a, b, x)
isa(a, Real) || (warn("$a is not a real number."); return :(throw(DomainError())))
isa(b, Real) || (warn("$b is not a real number."); return :(throw(DomainError())))
return :($a * $x + $b) # interpolate the variables
end
Here, #eval_line macro does the following:
Main> #macroexpand #eval_line(5, 6, 2)
:(5 * 2 + 6)
As you can see, the values of macro's arguments are interpolated inside the macro and the expression is given to the user accordingly. When the user does not behave,
Main> #macroexpand #eval_line([1,2,3], 7, 8)
WARNING: [1, 2, 3] is not a real number.
:((Main.throw)((Main.DomainError)()))
a user-friendly warning message is provided to the user at parse-time, and a DomainError is thrown at run-time.
Of course, you can do these things within your functions, again by interpolating the variables --- you do not need to use macros. However, what you would like to achieve in the end is to combine eval with the output of a function that returns Expr. This is what the macro functionality is for. Finally, you would simply call your macros with an # sign preceding the macro name:
Main> #eval_line(5, 6, 2)
16
Main> #eval_line([1,2,3], 7, 8)
WARNING: [1, 2, 3] is not a real number.
ERROR: DomainError:
Stacktrace:
[1] eval(::Module, ::Any) at ./boot.jl:235
EDIT 1. You can take this one step further, and create functions accordingly:
macro define_lines(linedefs)
for (name, a, b) in eval(linedefs)
ex = quote
function $(Symbol(name))(x) # interpolate name
return $a * x + $b # interpolate a and b here
end
end
eval(ex) # evaluate the function definition expression in the module
end
end
Then, you can call this macro to create different line definitions in the form of functions to be called later on:
#define_lines([
("identity_line", 1, 0);
("null_line", 0, 0);
("unit_shift", 0, 1)
])
identity_line(5) # returns 5
null_line(5) # returns 0
unit_shift(5) # returns 1
EDIT 2. You can, I guess, achieve what you would like to achieve by using a macro similar to that below:
macro random_oper(depth, fs, ts)
operations = eval(fs)
oper = operations[rand(1:length(operations))]
terminals = eval(ts)
ts = terminals[rand(1:length(terminals), 2)]
ex = :($oper($ts...))
for d in 2:depth
oper = operations[rand(1:length(operations))]
t = terminals[rand(1:length(terminals))]
ex = :($oper($ex, $t))
end
return ex
end
which will give the following, for instance:
Main> #macroexpand #random_oper(1, [+, -, /], [1,2,3])
:((-)([3, 3]...))
Main> #macroexpand #random_oper(2, [+, -, /], [1,2,3])
:((+)((-)([2, 3]...), 3))
Thanks Arda for the thorough response! This helped, but part of me thinks there may be a better way to do this as it seems too roundabout. Since I am writing a genetic program, I will need to create 500 of these ASTs, all with random functions and terminals from a set of allowed functions and terminals (fs and ts in the code). I will also need to test each function with 20 different values of x and v.
In order to accomplish this with the information you have given, I have come up with the following macro:
macro create_function(defs)
for name in eval(defs)
ex = quote
function $(Symbol(name))(x,v)
fs = [:+, :-, :DIV, :GT]
ts = [x,v,-1]
return create_single_full_tree(4, fs, ts)
end
end
eval(ex)
end
end
I can then supply a list of 500 random function names in my main() function, such as ["func1, func2, func3,.....". Which I can eval with any x and v values in my main function. This has solved my issue, however, this seems to be a very roundabout way of doing this, and may make it difficult to evolve each AST with each iteration.

MethodError, despite being (somehow) defined

I am trying to do something like this:
function outer(x::Array{Float64}, y::Array{Float64}=nothing)
if (y == nothing)
function inner(y::Array{Float64})
return x .* y
end
return inner
else
return x .+ y
end
end
outer([2.], [3.]) # to return 5, works
outer([2.])([3.]) # to return 6, fails.
outer([2.], [3.]) works just fine.
The problem is that outer([2.])([3.]) raises a MethodError stating:
MethodError: no method matching outer(::Array{Float64,1}, ::Void)
Closest candidates are:
outer(::Array{Float64,N} where N) at In[1]:2
outer(::Array{Float64,N} where N, ::Array{Float64,N} where N) at In[1]:2
Stacktrace:
[1] outer(::Array{Float64,1}) at ./In[1]:2
[2] include_string(::String, ::String) at ./loading.jl:522
The weird bit is that under Closest candidates, the single-argument outer(::Array{Float64,N} where N) is the first candidate. So why does it not work with the single argument?
Note: outer([2.], )([3.]), outer([2.], nothing)([3.]), outer([2.], [nothing])([3.]) all produce the same (similar) error.
This can be reproduced using a single argument function too:
function outer(y::Array{Float64}=nothing)
if (y == nothing)
function inner(y::Array{Float64})
return y .* y
end
return inner
else
return y .+ y
end
end
outer([2.])
1-element Array{Float64,1}:
4.0
outer()([3.])
MethodError: no method matching outer(::Void)
Closest candidates are:
outer() at In[6]:2
outer(::Array{Float64,N} where N) at In[6]:2
outer(::Array{Float64,N} where N, ::Array{Float64,N} where N) at In[1]:2
Stacktrace:
[1] outer() at ./In[6]:2
[2] include_string(::String, ::String) at ./loading.jl:522
And again, there is a zero-argument function outer() listed first in the Closest candidates list!
Basically, the above example is a MWE representing a log-sum/likelihood evaluation, where x is the data and y is the parameter of a model. I am trying to return a function in the parameter of the model to optimise using MLE, or return the log-sum if the parameter is passed.
In this analogy, outer computes log-sum, given data and parameter, or returns inner as a function of the parameter, which can be optimised.
You can reproduce this with simply:
f(x::Float64=nothing) = x
f()
When you add = nothing that sets nothing as a default argument. And also adds f() as a method. But when you call f() then julia will try to run f(nothing) as nothing is your default argument. That will then error as nothing is of type Void and you asserted that the argument must be Float64.
For example you could (but shouldn't) use f(x::Union{Float64,Void}=nothing) = x to get around this. But it'd be much better to use a Nullable which is exactly for interacting with a value that may or may not exist.
Read more about Nullables here or type ?Nullable in the REPL.
Edit by OP
MWE:
function outer(x::Array{Float64}, y=Nullable{Array{Float64}}())
if isnull(y)
function inner(y::Array{Float64})
return x .* y
end
return inner
else
return x .+ y
end
end
outer([2.])([3.]) # Produces 6,
outer([2.], [3.]) # Produces 5.
Works as expected.
Readability counts so maybe you have to reconsider define 2 methods explicitly (and simple as it is in Julian way):
outer(x::Array{Float64}, y::Array{Float64}) = x .+ y
outer(x::Array{Float64}) = y -> x .* y

Check if a function has keywords arguments in Julia

Is there a way to check if a function has keywords arguments in Julia? I am looking for something like has_kwargs(fun::Function) that would return true if fun has a method with keyword arguments.
The high level idea is to build a function:
function master_fun(foo::Any, fun::Function, ar::Tuple, kw::Tuple)
if has_kwargs(fun)
fun(ar... ; kw...)
else
fun(ar...)
end
end
Basically, #Michael K. Borregaard's suggestion to use try-catch is correct and officially works.
Looking into the unofficial implementation details, I came up with the followng:
haskw(f,tup) = isdefined(typeof(f).name.mt,:kwsorter) &&
length(methods(typeof(f).name.mt.kwsorter,(Vector{Any},typeof(f),tup...)))>0
This function first looks if there is any keyword processing on any method of the generic function, and if so, looks at the specific tuple of types.
For example:
julia> f(x::Int) = 1
f (generic function with 1 method)
julia> f(x::String ; y="value") = 2
f (generic function with 2 methods)
julia> haskw(f,(Int,))
false
julia> haskw(f,(String,))
true
This should be tested for the specific application, as it probably doesn't work when non-leaf types are involved. As Michael commented, in the question's context the statement would be:
if haskw(fun, typeof.(ar))
...
I don't think you can guarantee that a given function has keyword arguments. Check
f(;x = 3) = println(x)
f(x) = println(2x)
f(3)
#6
f(x = 3)
#3
f(3, x = 3)
#ERROR: MethodError: no method matching f(::Int64; x=3)
#Closest candidates are:
# f(::Any) at REPL[2]:1 got unsupported keyword argument "x"
# f(; x) at REPL[1]:1
So, does the f function have keywords? You can only check for a given method. Note that, in your example above, you'd normally just do
function master_fun(foo, fun::Function, ar::Tuple, kw....)
fun(ar... ; kw...)
end
which should work, and if keywords are passed to a function that does not take them you'd just leave the error reporting to fun. If that is not acceptable you could try to wrap the fun(ar...; kw...) in a try-catch block.

Resources