Function argument passing and function redefinition in Julia - julia

When I redefine a function that has been passed into another function, it seems the redefinition was not passed into that function.
function foo(f)
f(3)
end
f(x)=x
foo(f) #=>3
f(x)=x*x
foo(f) #=>3
f(3) #=>9
This behavior looks so weird to me. What's the logic behind this?

Running f(x)=x*x prompts a definition overwritten warning. The warning alerts to the possibility of this kind of behavior. In general, redefining functions when some other functions referencing it have already been compiled is tricky. As http://github.com/julialang/julia/issues/265 from 2011 indicates, this is an old issue.
A program can avoid this issue with care. For example, using anonymous functions with foo() in the question will give:
julia> foo(x->x)
3
julia> foo(x->x*x)
9
In Julia 0.6 this issue is resolved. The Github issue details the resolution, but essentially, Julia keeps track of a version number of the world, and a function 'sees' a certain version of the world. In the REPL, a redefinition causes the call to the older function to trigger a recompile (see http://docs.julialang.org/en/latest/manual/methods.html# for details). The resulting behavior is less weird:
julia> # version 6.0
julia> function foo(f)
f(3)
end
foo (generic function with 1 method)
julia> f(x)=x
f (generic function with 1 method)
julia> foo(f)
3
julia> f(x)=x*x
f (generic function with 1 method)
julia> foo(f)
9
julia> # :-)
Thanks to #StefanKarpinski, #ChrisRackauckas

Related

How to mark functions as `#deprecate`d?

(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

Change/access remote variable in remote process

I am new to Julia and trying to do a very simple task:
to distribute variable x=1 to every process
change x to a different value only in process 2 (change x local to process 2)
print the new value of remote x in process 2
My code is:
using Distributed
function f()
x=10*x
println(x)
end
#everywhere x=1
remote_do(f,2)
It does not print anything.
Another try with remotecall:
r=remotecall(x->10*x,2,x)
fetch(r)
println(x)
prints 10 (what anonymous function returns) and 1 (x in process 1) as expected.
As I understand remotecall returns a future with result of lambda x->10x but does not change the remote variable. In fact it even does not multiply the remote variable but x in process 1!
Question: How to change and read remote variable in process 1?
First, x is scoped locally to f(), so even running f() on the local process produces an error:
julia> f()
ERROR: UndefVarError: x not defined
If you really want to use a global variable here, you need to tell Julia that:
function f()
global x=10*x
println(x)
end
julia> f()
10
Then, to see why it isn't running remotely, you can try remotecall_fetch to make the call synchronously and see any exceptions (without this exceptions go to stderr on the remote worker).
julia> remotecall_fetch(f, 2)
ERROR: On worker 2:
UndefVarError: #f not defined
The remote worker does not have a definition for f.
#everywhere function f()
global x=10*x
println(x)
end
julia> remote_do(f,2)
julia> From worker 2: 10
To make this easier when you have more code, you could place the code in a module and then call #everywhere using MyModule.

Alternatives to parse and eval to check syntactic correctness for Julia v 1.1

Julia is dynamically typed and certain errors only occur during execution.
For instance:
julia> function foo()
a
5
end
foo (generic function with 1 method)
julia> foo()
ERROR: UndefVarError: a not defined
Stacktrace:
[1] foo() at ./REPL[1]:2
[2] top-level scope at none:0
julia>
Same behaviour using parse in combination with eval:
julia> eval(Meta.parse("function foo()
a
5
end"))
foo (generic function with 1 method)
However, when executing this an error is thrown:
julia> foo()
ERROR: UndefVarError: a not defined
Stacktrace:
[1] foo() at ./none:2
[2] top-level scope at none:0
Does it exist any standard facilities to check against these kinds of errors? Or does it exist any suitable packages for this task?
The VS Code Julia extension has an integrated linter that can detect usage of undefined variables, as in your example.
There is also this linter, Lint.jl, though I am not sure if it's up-to-date for Julia v1.x.

Julia macro doesn't recognize Array argument

I wrote a fairly complex macro that manipulates an array, but it didn't work. I get the same error message even after replacing my macro with something ridiculously simple:
macro len(arr::Array)
length(arr)
end
#len [1 2 3]
The error message I get is:
ERROR: LoadError: MethodError: no method matching #len(::Expr)
Why is Julia insisting on interpreting my input as an expression? I'm completely new to Julia, and there is obviously something I don't understand about either macros or the type system. Can someone please explain?
EDIT: Time to explain what my original macro was for. I want to take a table as input and use it to define variables in the local scope. The entries in the first column define individual variable names and the other columns define variable contents. Since a function can't do this, I need a macro. I have a working macro that takes a long (triple-quoted) string as input, but now I want it to take an array as input instead.
After this explanation, maybe I should add another subquestion: why does my other macro accept the appropriate input (it is defined as macro foo(text::String)) whereas the array version doesn't?
User Chris Rackauckas answered this on irc in discussion with me:
#len $a
macros are on expressions, not on the values, so
this sounds like something you'd want a function for
(if it's really needed at all).
I'm not sure this would work there anyway.
This isn't what macros are for..
it's for converting expressions into other expressions, mostly.
If you're trying to use macros on values, then you want a function
(you may be able to #eval or something to make this work here, but you'll be going through great lengths to make a macro act like a function)
..as a function:
julia> function len(x)
length(x)
end
len (generic function with 1 method)
julia> len(a)
3
julia> a
1x3 Array{Int64,2}:
1 2 3
To answer my own question: this is how that "ridiculously simple" macro should have been written.
macro len(expr)
:(length($expr))
end
This operates on an expression, not an array (thank you Chris Rackauckas and Isaiah for pointing that out). I was misled by my own experiments, since a similar macro for strings happens to work:
# MISLEADING CODE, DON'T USE THIS!
macro lenstr(str::String)
length(str)
end
Apparently Julia allows string macros, but this is an exception to the general rule that macros operate on expressions. To illustrate how arguments appear to the macro:
macro dump(arg)
dump(arg)
end
julia> #dump "foo bar"
String "foo bar"
julia> #dump [1,2,3]
Expr
head: Symbol vect
args: Array{Any}((3,))
1: Int64 1
2: Int64 2
3: Int64 3
typ: Any
julia> #dump [1 2 3]
Expr
head: Symbol hcat
args: Array{Any}((3,))
1: Int64 1
2: Int64 2
3: Int64 3
typ: Any
Note how [1,2,3] and [1 2 3] are seen by the macro as expressions, not arrays. This was the source of my error. By applying lessons learned here, my more complex macro that creates variables in a local scope from an array now works. Thank you all!

How to alias quit() to quit?

This is just a convenience but I think useful. Note that IPython allows a pure quit as does Matlab. Thus it would be reasonble in Julia to allow aliasing.
Thanks for any ideas as to how to do this.
Quitting in Julia
If you are using Julia from the command line then ctrl-d works. But if your intention is to quit by typing a command this is not possible exactly the way you want it because typing quit in the REPL already has a meaning which is return the value associated with quit, which is the function quit.
julia> quit
quit (generic function with 1 method)
julia> typeof(quit)
Function
Also Python
But that's not rare, for example Python has similar behavior.
>>> quit
Use quit() or Ctrl-D (i.e. EOF) to exit
Using a macro
Using \q might be nice in the Julia REPL like in postgres REPL, but unfortunately \ also already has a meaning. However, if you were seeking a simple way to do this, how about a macro
julia> macro q() quit() end
julia> #q
Causes Julia to Quit
If you place the macro definition in a .juliarc.jl file, it will be available every time you run the interpreter.
As waTeim notes, when you type quit into the REPL, it simply shows the function itself… and there's no way to change this behavior. You cannot execute a function without calling it, and there are a limited number of ways to call functions in Julia's syntax.
What you can do, however, is change how the Functions are displayed. This is extremely hacky and is not guaranteed to work, but if you want this behavior badly enough, here's what you can do: hack this behavior into the display method.
julia> function Base.writemime(io::IO, ::MIME"text/plain", f::Function)
f == quit && quit()
if isgeneric(f)
n = length(f.env)
m = n==1 ? "method" : "methods"
print(io, "$(f.env.name) (generic function with $n $m)")
else
show(io, f)
end
end
Warning: Method definition writemime(IO,MIME{symbol("text/plain")},Function) in module Base at replutil.jl:5 overwritten in module Main at none:2.
writemime (generic function with 34 methods)
julia> print # other functions still display normally
print (generic function with 22 methods)
julia> quit # but when quit is displayed, it actually quits!
$
Unfortunately there's no type more specific than ::Function, so you must completely overwrite the writemime(::IO,::MIME"text/plain",::Function) definition, copying its implementation.
Also note that this is pretty unexpected and somewhat dangerous. Some library may actually end up trying to display the function quit… causing you to lose your work from that session.
Related to Quitting in Julia
I was searching for something simple. This question hasn't been updated since 2017, as I try to learn Julia now, and spend some time googling for something simple and similar to python. Here, what I found:
You can use:
exit()
Note
I use julia 1.53

Resources