Is there an equivalent in Julia to the stop() function in R? The nice thing about stop() is that it can be run from any scope (e.g. a function within a function) and it will throw an error and terminate the function that the user called.
I see that throw() in Julia does something similar, but I’d like to avoid printing a stack trace because it is not necessary for the info I’m trying to convey to users (e.g. two arrays provided as arguments are not the same size, but they need to be)
@assert is generally what I would use for that, but it does print a stacktrace. It is a bit of a shorthand for what you are already doing.
julia> function gg(x,y)
@assert x != y
x+y
end
gg (generic function with 1 method)
julia> gg(1,1)
ERROR: AssertionError: x != y
Stacktrace:
[1] gg(::Int64, ::Int64) at ./REPL[49]:2
[2] top-level scope at REPL[50]:1
Thanks! Is there any way to suppress the stacktrace?
Ideally, the output would just look like this (no stacktrace, just a link to the line of code that causes it for developers’ reference):
┌ Error: A detailed error message
└ @ Main untitled-8dbe96a72552bcb86ffb481d3a092170:2
So that I can tell my users what is wrong (concisely) so they can fix it and try again. The user base I’m working with consists mostly of non-programmers, so I’m hoping to make things as straightforward and simple as possible.
struct Stop{T}
answer::T
end
function stop(answer)
throw(Stop(answer))
end
function start()
stop(42)
end
try
start()
@info "No answer."
catch ex
if isa(ex, Stop)
@info "Exit with: $(ex.answer)"
else
rethrow(ex)
end
end
This assumes that you are not already catching exceptions in the stack. If you are then you probably want to throw a “Stop” object that the other exception handlers can rethrow.
This is also not the approved way of unwinding the stack, throw is meant for errors not a normal exit.
I think it may be the perfect solution for this use case.
Note also that R’s stop() is something like this, except that R, by default, does not print a stacktrace — the user can request one with traceback(). I think that the idea is that error messages themselves convey enough information, which I do not think is true for any nontrivial computation.
Agreed @Tamas_Papp – in my case, it’ll something like an input matrix having negative values when it shouldn’t, or matrix 1 not having the same dims as matrix 2, so really simple stuff.
Would I need to put that try/catch in the top-level function whenever I want to check for an error? I tried playing around with putting it inside a function within a function and wasn’t able to figure it out.
I’m a little surprised this is so hard. I would have assumed that we’d have facilities for suppressing stacktraces.
One (non-ideal) approach to this would be to create a custom REPL mode where stack-traces may be suppressed. You can do this with my package ReplMaker.jl. Here’s what that might look like:
using ReplMaker
using REPL: LineEdit
struct QuietError{T}
msg::T
end
globalize(ex) = ex
function globalize(ex::Expr)
if ex.head == :block
ex.args = globalize.(ex.args)
ex
elseif ex.head == :(=) || ex.head == :function
:(global $ex)
else
ex
end
end
function suppressable_stacktrace_parser(s)
ex = globalize(Meta.parse(s))
quote
try
$ex
catch e
if e isa QuietError
@info "Exit with $e"
else
rethrow(e)
end
end
end
end
iscomplete(x) = true
function iscomplete(ex::Expr)
if ex.head == :incomplete
false
else
true
end
end
function valid_julia(s)
input = String(take!(copy(LineEdit.buffer(s))))
iscomplete(Meta.parse(input))
end
Now we can do
julia> initrepl(suppressable_stacktrace_parser,
prompt_text="quiet-julia> ",
prompt_color = :blue,
start_key=')',
mode_name="Suppressable-Error-Mode",
valid_input_checker=valid_julia);
REPL mode Suppressable-Error-Mode initialized. Press ) to enter and backspace to exit.
julia> )
quiet-julia> f(x) = g(x + 1)
f (generic function with 1 method)
quiet-julia> g(x) = h(x/2)
g (generic function with 1 method)
quiet-julia> function h(x)
if x < 0
throw(QuietError("x must be positive, got $x"))
end
x
end
h (generic function with 1 method)
quiet-julia> h(-5)
[ Info: Exit with QuietError{String}("x must be positive, got -5")
Yeah, I found the potential workarounds to be too tricky to work with, so for now, I decided I would proceed with an approach similar to what I outlined in the origin post. I simplified it a bit so that check_for_the_error(arg1, arg2) will also throw the error message if needed and return a Bool specifying whether the error was triggered. So now I have this inside my top-level function:
check_for_the_error(arg1, arg2) && return
Not sure if this is the most ideal or elegant approach – but it was the cleanest I could get it, and works for my purposes.
If you want to do something else than unwind and show the stack (eg provide an error message to the user), yes, you need a try / catch.
It can be in any caller, does not need to be top level.
That said, I don’t see why you are trying to avoid printing the stack trace. It is customary to do so in Julia (to the extent that you have to work hard to avoid it), and it aids debugging by providing some context for the error. This is important for generic code where methods can show up in a lot of different contexts.
I hear you. My package is essentially one user-facing function. Users have to provide file paths to spatial data as arguments, and I want to throw an error for my users (who are not programmers) if the data layers do not align spatially, so they can go into a GIS program and fix them. I worry that anything else may just add to potential confusion.