-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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 default parameter to numerical tryparse #37170
Conversation
I don't like this API. Quite a number of functions return |
Agree that just using |
That's fair. I wish Maybe a function can be added in |
Well, imagine if |
Corrupted data vs missing data are two different things, to me at least. |
I guess this is there to make it consistent with |
How do you suppose the data became But fair enough. @nalimilan do you have any ideas where parsing that returns |
Missings.jl would be a natural home if we wanted to add such a function. But this PR would sound like a more logical addition to me (I like the analogy with |
base/parse.jl
Outdated
# Zero base means, "figure it out" | ||
tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), false) | ||
val = tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), false) | ||
isnothing(default) ? val : something(val, default) |
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.
AFAICT this should be either something(val, default)
or default === nothing ? val : default
, not both. The latter is safer if T
can be <: Some
. (=== nothing
is better than isnothing
for inference)
Have you considered the other approach, which is to pass default
to tryparse_internal
and have it return that instead of nothing
? It could be slightly faster (not sure).
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.
something(val, default)
errors if both val
and default
are nothing
, and default === nothing ? val : default
will always return default
if one is passed in.
I was hoping to avoid clutter by not propagating to tryparse_internal
, but I'll run some benchmarks to see if there is a speedup.
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.
something(val, default)
errors if bothval
anddefault
arenothing
, anddefault === nothing ? val : default
will always returndefault
if one is passed in.
Ah, yes, of course. Though using default === nothing
instead of isnothing(default)
might still be better for the compiler.
I was hoping to avoid clutter by not propagating to
tryparse_internal
, but I'll run some benchmarks to see if there is a speedup.
I can't be sure without trying, but I had the impression that adding the argument to tryparse
could actually be cleaner, as it would just require replacing nothing
with default
everywhere, instead of adding special code to replace nothing
after the fact. But I might be wrong.
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've added a commit with these changes but didn't observe any significant difference once compiled into a sysimage. Also, tests are now failing as some stdlibs depend on Base.tryparse_internal
, so I'm not convinced adding the argument to tryparse_internal
is a good idea.
Another thing to consider is whether we should enforce default::Union{T,Missing,Nothing}
or allow default::Any
, in which case we would need a workaround for the Float16
implementation.
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've added a commit with these changes but didn't observe any significant difference once compiled into a sysimage.
Yeah, I wasn't saying it would necessarily make a difference in this case, it's just better in general.
Also, tests are now failing as some stdlibs depend on
Base.tryparse_internal
, so I'm not convinced adding the argument totryparse_internal
is a good idea.
Why not use default = nothing
in tryparse_internal
so that it keeps returning nothing
if you don't provide the argument?
Another thing to consider is whether we should enforce
default::Union{T,Missing,Nothing}
or allowdefault::Any
, in which case we would need a workaround for theFloat16
implementation.
What's the problem with Float16
?
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.
Why not use
default = nothing
intryparse_internal
so that it keeps returningnothing
if you don't provide the argument?
Then you have a default value for default
in both tryparse
and tryparse_internal
, which means you could accidentally stop passing default
from tryparse
to tryparse_internal
in the future and not see any issues. Although I suppose that's mitigated by having a broad range of tests.
What's the problem with
Float16
?
tryparse(::Type{Float16}, s::AbstractString) =
convert(Union{Float16, Nothing}, tryparse(Float32, s))
Float16
is "parsed" by parsing as a Float32
and then convert
ing to Float16
, so this would need something like val === nothing ? default : val
. But from the above conversation
We know that the value should be a Float64, but we unfortunately don't know what the value is since the input was bad.
I think we might want to annotate the default
s with ::Union{T,Missing,Nothing}
to be type-stable.
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.
Then you have a default value for
default
in bothtryparse
andtryparse_internal
, which means you could accidentally stop passingdefault
fromtryparse
totryparse_internal
in the future and not see any issues. Although I suppose that's mitigated by having a broad range of tests.
Right, but I don't think that's a problem. As you say, tests are there to check that it works.
Float16
is "parsed" by parsing as aFloat32
and thenconvert
ing toFloat16
, so this would need something likeval === nothing ? default : val
.
Yeah, but I guess it's OK as long as it's only in one method.
I think we might want to annotate the
default
s with::Union{T,Missing,Nothing}
to be type-stable.
Why would this affect affect type stability? :-/
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.
Why would this affect affect type stability? :-/
I guess tryparse
isn't a type-stable function to begin with since it could return nothing
(maybe I'm misunderstanding type stability?), but if tryparse
returns a value that is neither nothing
or missing
, shouldn't it be of type T
? Or can we assume the user supplying default
will handle it?
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.
tryparse(T, str, default)
should be inferred by the compiler as returning Union{T, typeof(default)}
, and yes, that's the user's job to handle the default
he provided.
1b47bc2
to
ec43b0a
Compare
@@ -227,79 +227,81 @@ end | |||
end | |||
|
|||
""" | |||
tryparse(type, str; base) | |||
tryparse(type, str, default; base) |
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.
tryparse(type, str, default; base) | |
tryparse(type, str, default=nothing; base) |
base/parse.jl
Outdated
# Zero base means, "figure it out" | ||
tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), false) | ||
val = tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), false) | ||
isnothing(default) ? val : something(val, default) |
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've added a commit with these changes but didn't observe any significant difference once compiled into a sysimage.
Yeah, I wasn't saying it would necessarily make a difference in this case, it's just better in general.
Also, tests are now failing as some stdlibs depend on
Base.tryparse_internal
, so I'm not convinced adding the argument totryparse_internal
is a good idea.
Why not use default = nothing
in tryparse_internal
so that it keeps returning nothing
if you don't provide the argument?
Another thing to consider is whether we should enforce
default::Union{T,Missing,Nothing}
or allowdefault::Any
, in which case we would need a workaround for theFloat16
implementation.
What's the problem with Float16
?
It's worrying to me how the data-community keeps trying to add features to Base that scales with |
ec43b0a
to
1cbf4b9
Compare
I'm also against this. Returning nothing vs. non-nothing is a legitimate style of API used in many places. Callers should handle the |
The argument that this is similar to |
Indeed. Actually, Maybe worth revisiting now, in the perspective of deprecating tryparse in 2.0. |
Bumping this, a 3 argument |
Triage says tryparse doesn't need a default since users can use |
Should fix #37162, although I wonder if there's a cleaner way to do this.