How to mark functions as `#deprecate`d? - julia

(Question refers to Julia version v1.5)
I'm trying to understand how the #deprecate macro works in Julia. The documentation is unfortunately not clear to me:
#deprecate old new [ex=true]
Deprecate method old and specify the replacement call new. Prevent
#deprecate from exporting old by setting ex to false. #deprecate
defines a new method with the same signature as old.
Warning:
As of Julia 1.5, functions defined by #deprecate do not print warning when julia is run without the --depwarn=yes flag set, as the default value of --depwarn option is no. The warnings are printed from tests run by Pkg.test().
Examples
julia> #deprecate old(x) new(x)
old (generic function with 1 method)
julia> #deprecate old(x) new(x)
false old (generic function with 1 method)
So what do I do?
function old(x::Int)
print("Old behavior")
end
function new(x::Int)
print("New behavior")
end
# Adding true/false argument doesn't change my observations.
#deprecate old(x) new(x) # false
old(3)
# Prints "Old behaviour". No warning.
# Also: The deprecation is not mentioned in the help (julia>? old)
The aim of this #deprecate macro seems to be replacing functions? I find that counterintuitive. How can mark a function as deprecated (i.e. users should receive a warning and a hint what to use as a replacement, also it should be in the documentation)?
edit: I noticed my error. The signatures (in my case the ::Int) have to be identical for this to work. However, how do I get a warning?

Imagine you have this method as part of the public API of your library in version 1:
# v1.0.0
mult3(x::Int) = 3x
In version 2, you'd like to stop supporting mult3 (which is a breaking change). But the same feature will still be available using a more general method:
# v2.0.0
mult(x, y) = x * y
Users of version 1 are used to using mult3, which means that their code will break when they will update to v2. Therefore, you might want to release an intermediate version in the v1.x family, where mult3 exists but is deprecated and implemented in terms of mult:
# v1.1 - transition
# This is the new API for v2
mult(x, y) = x*y
# The old API is still supported, but deprecated and implemented using the old one
#deprecate mult3(x::Int) mult(3, x)
# The above is more or less equivalent to defining
# function mult3(x::Int)
# # print an error message is `--depwarn` has been set
# return mult(3, x)
# end
The v1 API is not broken in late v1.x versions, but users calling deprecated methods will see the following kind of messages to help them transition to the newer v2 API:
julia> mult3(14)
┌ Warning: `mult3(x::Int)` is deprecated, use `mult(3, x)` instead.
│ caller = top-level scope at REPL[3]:1
└ # Core REPL[3]:1
42
(but starting with Julia 1.5, the warning will only be shown if --depwarn=yes has been provided in Julia's command line or if it appears in a test suite run by Pkg.test())
Alternatively, and as mentioned in comments, you may want to leave the old implementation around, simply warning users when they call it. To this end, you can use Base.depwarn directly:
# v1.1 - transition
# This is the new API for v2
mult(x, y) = x*y
# The old API is still supported, but deprecated
# It is implemented by itself:
function mult3(x)
Base.depwarn("`mult3(x)` is deprecated, use `mult(3,x)` instead.", :mult3)
return 3x
end
When --depwarn=yes has been provided in Julia's command line, this produces the same kind of warning as #deprecate:
julia> mult3(14)
┌ Warning: `mult3(x)` is deprecated, use `mult(3,x)` instead.
│ caller = top-level scope at REPL[4]:1
└ # Core REPL[4]:1
42
Starting with Julia 1.6, depwarn will accept a keyword argument to force warning emission even when users haven't asked for them with --depwarn=yes:
julia> Base.depwarn("Foo is deprecated", :foo, force=true)
┌ Warning: Foo is deprecated
│ caller = ip:0x0
└ # Core :-1

Related

Julia CUDA: UndefVarError: parameters not defined

I have a program for doing Fourier series and I wanted to switch to CuArrays to make it faster. The code is as follows (extract):
#Arrays I want to use
coord = CuArray{ComplexF64,1}(complex.(a[:,1],a[:,2]))
t=CuArray{Float64,1}(-L:(2L/(N-1)):L)
#Array of indexes in the form [0,1,-1,2,-2,...]
n=[((-1)^i)div(i,2) for i in 1:grado]
#Array of functions I need for calculations
base= [x -> exp(π * im * i * x / L) / L for i in n]
base[i](1.) #This line is OK
base[i](-1:.1:1) #This line is OK
base[i].(t) #This line gives error!
base[i].(CuArray{Float64,1}(t)) #This line gives error!
And the error is:
GPU broadcast resulted in non-concrete element type Any.
This probably means that the function you are broadcasting contains an error or type instability.
If I change it like this
base= [(x::Float64) -> (exp(π * im * i * x / L) / L)::ComplexF64 for i in n]
the same lines still give error, but the error now is:
UndefVarError: parameters not defined
Any idea how I could fix this?
Thank you in advance!
Package information:
(#v1.6) pkg> st CUDA
Status `C:\Users\marce\.julia\environments\v1.6\Project.toml`
[052768ef] CUDA v2.6.2
P.S.: This other function has the same problem:
function integra(inizio, fine, arr)
N=size(arr,1)
h=(fine-inizio)/N
integrale=sum(arr)
integrale -= (first(arr)+last(arr))/2
integrale *= h
end
L=2
integra(-L,L,coord)
The first and easier problem is that you should take care to declare global variables to be constant so that the compiler can assume a constant type: const L = 2. A mere L = 2 allows you to do something like L = SomeOtherType(), and if that type can be Anything, so must the return type of your functions. On the CPU that's only a performance hit, but it's a no-no for the GPU. If you actually want L to vary in value, pass it in as an argument so the compiler can still infer types within a function.
Your ::ComplexF64 assertion did actually force a concrete return type, though the middle of the function is still type unstable (check with #code_warntype). The second problem you ran into after that patch was probably caused by this recently patched conflict between ExprTools.jl and LLVM.jl. Seems like you just need to update the packages or maybe reinstall them.

Delete a Method in Julia

I have a function with 2 methods
function foo(a::Integer) 42 end
function foo(a::String) 24 end
foo(2)
42
foo("a")
24
How can I delete just one of the two methods?
There is a type Method. Instances of that type refer to a specific method of a particular function.
particular_method = #which foo(2)
foo(a::Integer) in Main at /home/js/Documents/Julia/Matching/Query_Creation.jl:75
typeof(particular_method)
Method
And here's a way to delete the method using such an object:
Base.delete_method(particular_method)
foo(2)
ERROR: MethodError: no method matching foo(::Int64)
If you're writing your code in a file tracked by Revise, then deleting the method in the source will delete it in your running session. If I copy your two definitions to a file /tmp/delmeth.jl, then
julia> using Revise
julia> Revise.includet("/tmp/delmeth.jl")
julia> foo(2)
42
julia> foo("a")
24
Now, in your editor, delete the method for foo(::String), save the file, and in the same session do this:
julia> foo(2)
42
julia> foo("a")
ERROR: MethodError: no method matching foo(::String)
Closest candidates are:
foo(::Integer) at /tmp/delmeth.jl:1
Stacktrace:
[1] top-level scope
# REPL[6]:1
If you're developing anything "serious" & reusable, then you should generally create packages, in which case you don't need includet because Revise automatically tracks packages that you've loaded with using or import. See the Revise docs for further details.

What does the "Base" keyword mean in Julia?

I saw this example in the Julia language documentation. It uses something called Base. What is this Base?
immutable Squares
count::Int
end
Base.start(::Squares) = 1
Base.next(S::Squares, state) = (state*state, state+1)
Base.done(S::Squares, s) = s > S.count;
Base.eltype(::Type{Squares}) = Int # Note that this is defined for the type
Base.length(S::Squares) = S.count;
Base is a module which defines many of the functions, types and macros used in the Julia language. You can view the files for everything it contains here or call whos(Base) to print a list.
In fact, these functions and types (which include things like sum and Int) are so fundamental to the language that they are included in Julia's top-level scope by default.
This means that we can just use sum instead of Base.sum every time we want to use that particular function. Both names refer to the same thing:
Julia> sum === Base.sum
true
Julia> #which sum # show where the name is defined
Base
So why, you might ask, is it necessary is write things like Base.start instead of simply start?
The point is that start is just a name. We are free to rebind names in the top-level scope to anything we like. For instance start = 0 will rebind the name 'start' to the integer 0 (so that it no longer refers to Base.start).
Concentrating now on the specific example in docs, if we simply wrote start(::Squares) = 1, then we find that we have created a new function with 1 method:
Julia> start
start (generic function with 1 method)
But Julia's iterator interface (invoked using the for loop) requires us to add the new method to Base.start! We haven't done this and so we get an error if we try to iterate:
julia> for i in Squares(7)
println(i)
end
ERROR: MethodError: no method matching start(::Squares)
By updating the Base.start function instead by writing Base.start(::Squares) = 1, the iterator interface can use the method for the Squares type and iteration will work as we expect (as long as Base.done and Base.next are also extended for this type).
I'll grant that for something so fundamental, the explanation is buried a bit far down in the documentation, but http://docs.julialang.org/en/release-0.4/manual/modules/#standard-modules describes this:
There are three important standard modules: Main, Core, and Base.
Base is the standard library (the contents of base/). All modules
implicitly contain using Base, since this is needed in the vast
majority of cases.

How do you check if a variable is defined inside a module in Julia?

isdefined(:x) will tell you if a variable x is defined in your current workspace.
If I want to check a variable is defined in a module (not one that's exported), how can I do that? I tried all of the following:
julia> module Test
x = 1
end
Test
julia> x
ERROR: UndefVarError: x not defined
julia> isdefined(:x)
false
julia> Test.x
1
julia> isdefined(:Test.x)
ERROR: type Symbol has no field x
julia> isdefined(:Test.:x)
ERROR: TypeError: getfield: expected Symbol, got QuoteNode
julia> isdefined(Test.:x)
ERROR: TypeError: getfield: expected Symbol, got QuoteNode
In the module Test above, I want to check if x is defined or not.
isdefined has an optional parameter for doing this. Try:
isdefined(Test, :x)
More information available through the usual channels: ?isdefined on the REPL and in the book: http://docs.julialang.org/en/release-0.4/stdlib/base/#Base.isdefined (link may be for older version, so the currently dominant search engine will help).
I think you need
:x in names(Test)
In Julia 1.1.0, isdefined's first parameter is not optional, instead there is a macro #isdefined(x) or #isdefined x that tests if x is defined. Calling it inside Test, checks if x is defined when in Test (inherited or not).
See documentation.

Accessing to the AST of a function in Julia

In Julia, it is possible to view the AST of a user defined function:
julia> myFunc(x) = 5*x+3
myFunc (generic function with 1 method)
julia> tmp = dump(quote myFunc end)
Expr
head: Symbol block
args: Array(Any,(2,))
1: Expr
head: Symbol line
args: Array(Any,(2,))
1: Int64 1
2: Symbol none
typ: Any
2: Symbol myFunc
typ: Any
Which is the AST I am interested in. However, the variable tmp doesn't contain the Expr representing the syntactic tree I am expecting:
julia> tmp
julia> typeof(tmp)
Nothing (constructor with 1 method)
Is there another way to get this Expr? (the one that is displayed when running dump(quote myFunc end) )
dump does not give you the result; it is just a way of printing the value. (As you saw, it prints as a side-effect and returns a nothing.)
What you gave dump was an AST containing the name of your function, not the function itself. dump is not printing out a representation of your function: it is saying it has a block of one line containing the symbol myFunc.
If you want the AST, you should run code_typed(myFunc,(Any,)) or code_lowered(myFunc,(Any,)). For other functions, you will need different and/or more specific type signatures as the second argument.
If you are only planning to call myFunc with Ints or Float64s or whatever, use that instead of Any -- it will make a difference to code_typed's output, since the type inference will change.
I wrote a blog post documenting the code_typed/code_lowered set of functions: http://blog.leahhanson.us/julia-introspects.html
(I also spend time in that post looking at their output, the Expr type and explaining it's structure.)
The Metaprogramming section of the official manual will probably be useful to you in working with ASTs, if you haven't already read it.
You can't access the AST of a function, because a function is a a collection of methods (that might be implemented differently) in Julia. If it suits your needs you should use the documented code_typed function, where you specify the types of the arguments to select the right method. There are also some hints in Access the AST for generic functions in Julia, but that is not documented functionality, so it might change without warning.

Resources