-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional error message to only
to simplify usage
#43729
Conversation
This just came up on slack where Mosè Giordano pointed out this line should use This tiny patch would allow the error message to be included in the call to |
Are we planning on dispatching on the type of |
Also, it seems to me that the |
Maybe a macro would be more generalizable? Then you could throw a warning, or print a message, too?
|
The downside of this is that any message will be created unconditionally, e.g. this string interpolation will happen even when there is no error:
I'm not sure that matters there, but in tighter loops 100ns or whatever will hurt. It could solve this by being |
base/iterators.jl
Outdated
Stacktrace: | ||
[...] | ||
``` | ||
""" | ||
@propagate_inbounds function only(x) | ||
@propagate_inbounds function only(x, message="must contain exactly 1 element") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@propagate_inbounds function only(x, message="must contain exactly 1 element") | |
@propagate_inbounds function only(x; hint::String="") |
base/iterators.jl
Outdated
i = iterate(x) | ||
@boundscheck if i === nothing | ||
throw(ArgumentError("Collection is empty, must contain exactly 1 element")) | ||
throw(ArgumentError("Collection is empty, $message")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw(ArgumentError("Collection is empty, $message")) | |
str = "Collection is empty, must contain exactly 1 element" | |
if hint !== "" | |
str = "$(str); $(hint)" | |
end | |
throw(ArgumentError(str)) |
base/iterators.jl
Outdated
end | ||
(ret, state) = i::NTuple{2,Any} | ||
@boundscheck if iterate(x, state) !== nothing | ||
throw(ArgumentError("Collection has multiple elements, must contain exactly 1 element")) | ||
throw(ArgumentError("Collection has multiple elements, $message")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw(ArgumentError("Collection has multiple elements, $message")) | |
str = "Collection has multiple elements, must contain exactly 1 element" | |
if hint !== "" | |
str = str * "; " * hint | |
end | |
throw(ArgumentError(str)) |
@@ -1372,10 +1372,11 @@ IteratorEltype(::Type{Stateful{T,VS}}) where {T,VS} = IteratorEltype(T) | |||
length(s::Stateful) = length(s.itr) - s.taken | |||
|
|||
""" | |||
only(x) | |||
only(x, [message]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only(x, [message]) | |
only(x; hint::String = "") |
|
||
Return the one and only element of collection `x`, or throw an [`ArgumentError`](@ref) if the | ||
collection has zero or multiple elements. | ||
collection has zero or multiple elements. An optional error message can be provided to customize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
collection has zero or multiple elements. An optional error message can be provided to customize | |
collection has zero or multiple elements. An optional error hint can be provided to customize |
Stacktrace: | ||
[...] | ||
|
||
julia> only(('a', 'b'), "my error message") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
julia> only(('a', 'b'), "my error message") | |
only(('a', 'b'); hint = "my error hint") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bramtayl I think that macros are generally more flexible, but I think that would be an addition outside the scope here.
@mcabbott Good point about performance. I think that @DilumAluthge 's changes are constructive in the spirit of avoiding unnecessary interpolation.
@DilumAluthge Regarding your changes to move to keyword arguments, is there actually a significant win over a default value approach? I can't see that this is a hotbed of multiple dispatch, although one could imagine that it might become such (for instance, passing in a fully constructed exception to raise instead of the normal ArgumentException) but I can't see a big difference either way. If you feel there even a modest wind blowing toward the keyword approach, I am fine with it. I would tune the example slightly. I might also go further by adding message and hint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that making it a keyword will change the interpolation cost. The user's string is still made before the function is called.
What could perhaps avoid that without a macro is to accept e.g. only(xs, "expected ", typeof(x), " to have one element ")
, crudely:
only(things, message...) = length(things)==1 ? things[1] : error(string(message...))
But that also seems a bit weird, and you have to remember to write it that way.
I'm not sure if this is the right approach. Would it be better to instead use the cc: @timholy |
It would be good for triage to take a look at this. Here are some specific questions for triage to consider:
|
I noticed just now that there are more forms that need handling. The question about whether we dispatch on the presence of the error message is now more interesting. Is it better to dispatch to a different entry point? Or will the extra method that Julia inserts without the default all just melt away under inlining (even down to the conditional on the presence of the message). |
Question number 5 in particular ran me off the cliff. My general feeling is that overworking this is probably not worth it. Y'all are being amazingly responsive, but I am sure there are bigger things pending that I wouldn't want held up. If you feel this is worth it, cool. If not, also cool. |
An alternate approach might be something like Then, you can define To address the issue of paying the interpolation cost even when the error isn't thrown, you could do something like this: depname = "DataFrames"
x = only(collection, () -> MyCustomExceptionType(depname)) And then you don't actually perform the string interpolation until the If we switch the order of the arguments to be depname = "DataFrames"
x = only(collection) do
MyCustomExceptionType(depname)
end |
Don't worry too much about question 5. I'm not very familiar with the But that's why I want someone that's familiar with |
This is quite strange API, it doesn't exist anywhere else I think? Something like |
That sounds like a good approach! |
Allowing a Callable exception argument is quite reasonable although it forces a slightly more verbose idiom. We don't have to use every cleverness in julia for this simple extension. Folks lived without any way to customize the error message for quite some time. |
I just reviewed the test results. The tester_linux64 failure that I see is a bit of a mystery. Parallel builds of the same sort succeeded and the test that failed was a process issue. It seems that the test process just fell over due to external issues. |
I agree, I don't think we should add arguments to functions just to customize their internal error handling, especially if there's a performance cost. Base doesn't have a lot of APIs like this and the ones which do exist are slightly weird anomalies (Eg, However Dilum's # A way to provide defaults
x = only(collection) do
101
end
# Works well when the default is a costly computation
x = only(collection) do
expensive_computation()
end
# Also works for custom error messages
x = only(collection) do
error("Oops your collection was empty")
end All the expressions above have equivalents with |
A long time ago we used to have a function version of |
The do block approach is very nice they way you can either provide a default or throw an error. Very visible. |
From triage: please go ahead with the function argument ( |
Will do.
I will experiment to see how hard it would be to do the same with similar
functions like first or last.
…On Thu, Jan 20, 2022 at 11:39 AM Jeff Bezanson ***@***.***> wrote:
From triage: please go ahead with the function argument (do block)
implementation.
—
Reply to this email directly, view it on GitHub
<#43729 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAB5E6WKFB4QVHORD4GHD3TUXBQFZANCNFSM5LSMP4GQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Uses of only are often replaced (or just not done in the first place) with explicit logic to get better error messages. Allowing the error message to be passed in would prevent this and make
only
more useful.