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
Make vec_cast()
a directional coercion generic
#606
Comments
Just in terms of terminology, does this make:
|
Yes exactly. Also |
I just wanted to clarify something that I think is an important point here, and while I think @lionel- understands this well, it wasn't clear to me until now. I'm going to call this directional coercion generic As a directional coercion generic:
This would make The only way to do Some concrete examples of my thinking are below: library(vctrs)
vec_coerce <- vctrs:::vec_coercible_cast
vec_force <- vec_cast
# common type is numeric()
vec_ptype2(numeric(), integer())
#> numeric(0)
# meaning that of course we can do `integer -> numeric`
vec_coerce(integer(), numeric())
#> numeric(0)
# BUT it also means we are allowed to do `numeric -> integer`
# as a coercion as long as it is not lossy
vec_coerce(numeric(), integer())
#> integer(0)
vec_coerce(1.5, integer())
#> Error: Lossy cast from `x` <double> to `to` <integer>.
#> * Locations: 1
# Compare that with character() and integer(), which has no common type
vec_ptype2(character(), integer())
#> Error: No common type for `x` <character> and `y` <integer>.
# Meaning we can't even do this because there is no common type
vec_coerce(1L, character())
#> Error: No common type for `x` <integer> and `to` <character>.
# Or this, even though it isn't lossy
vec_coerce("1", integer())
#> Error: No common type for `x` <character> and `to` <integer>.
# To do this, you'd have to use `vec_force()`
vec_force(1L, character())
#> [1] "1"
vec_force("1", integer())
#> [1] 1
# Which would work as long as it is not lossy
vec_force("1.5", integer())
#> Error: Lossy cast from `x` <character> to `to` <integer>.
#> * Locations: 1 Created on 2019-11-21 by the reprex package (v0.3.0.9000) |
Maybe a good terminology would be:
|
Just a note, but if we use promotion, we'll need to be careful that it matches the direction in our diagrams. |
Does this resolve the following: vctrs::vec_cast("a", NA)
#> Error: Lossy cast from `x` <character> to `to` <logical>.
#> * Locations: 1
vctrs::`vec_slice<-`(NA, 1, "a")
#> Error: Lossy cast from `value` <character> to `x` <logical>.
#> * Locations: 1 Created on 2020-01-02 by the reprex package (v0.3.0) |
@krlmlr This looks like correct behaviour. vec-assign already uses directional coercion. |
Do we treat a vector of |
See tidyverse/tibble#689 for context: Do we support creating an |
We could make that exception, but I'm not sure that we should. Perhaps better wait and gain more experience with this restriction enabled, as we switch to vctrs in other packages? |
tidyr users have started to use the unrestricted conversion semantics of |
There are two issues with the current casting rules in vctrs:
They make vctrs complicated. Users and class implementors need to know about two diagrams for coercions and casts.
The casting rules do not seem to be the right default most of the time. For instance input validation can't use
vec_cast()
because it is too flexible.To fix this, we could make
vec_cast()
a directional coercion.A non-directional coercion converts an object to the common type, which is always the larger type (i.e. integer is coerced to double).
A directional coercion converts an object to the type supplied by the caller, but only if there exists a common type. A directional coercion is no longer guaranteed to convert to the larger type. If the cast is lossy, this is an error by default, unless the code is wrapped in
vctrs::allow_lossy_cast()
.Essentially, we'd add the rule that
vec_cast()
methods can only be implemented for coercible types, i.e. types that have a common type specified byvec_ptype2()
methods.Cast methods to remove
It is unclear yet how much of a breaking change it would be to remove these methods, we might need a deprecation strategy:
Where should this conversion logic go? Either we move it to separate single-dispatch generics like
vec_as_double()
, or we add a new double-dispatch generic in vctrs. @DavisVaughan suggested the namevec_force()
.Even if we have a double-dispatch generic, it is nice to have the explicit is_ and as_ functions for a class, so we might end up with separate wrappers anyway.
The separate generics have the advantage of requiring only a single dispatch, which makes things faster for users and simpler for class authors.
At a syntax level, having a single double-dispatch generic seems to give a false sense of principled genericity, whereas most of the implemented behaviour is ad hoc.
On the other hand,
vec_force()
could be useful as part of a deprecation plan.The text was updated successfully, but these errors were encountered: