convert(from, to) is a built-in generic for converting an object from
one type to another. It is special in four ways:
It uses double-dispatch, because conversion depends on both
fromandto.It uses non-standard dispatch because
tois a class, not an object.It doesn't use inheritance for the
toargument. To understand why, imagine you have written methods to objects of various types toclassParent. If you then create a newclassChildthat inherits fromclassParent, you can't expect the methods written forclassParentto work because those methods will returnclassParentobjects, notclassChildobjects.fromuses ordinary inheritance, so a method registered on a parent class is also used for its children, with two exceptions. Iffromis already an instance ofto, it's returned unchanged and no dispatch is needed. When upcasting (i.e.tois an ancestor offrom),convert()will never dispatch to a method registered ontoor one of its ancestors, because such a method would downcast.
convert() provides three default implementations:
When
frominherits fromto, it strips any properties thatfrompossesses thattodoes not (upcasting).When
toinherits fromfrom, it creates a new object of classto, copying over existing properties fromfromand initializing new properties ofto(downcasting).When
tois a base type (e.g. class_integer or class_character) and neither of the above apply, it calls the correspondingas.*()function (e.g.as.integer()oras.character()). This mirrors the convention thatas.*()coercion sits belowconvert(), so you can rely on it as a fallback but still override it with a more specific method.
If you are converting an object solely for the purposes of accessing a method
on a superclass, you probably want super() instead. See its docs for more
details.
Arguments
- from
An S7 object to convert.
- to
An S7 class specification, passed to
as_class().- ...
Other arguments passed to custom
convert()methods. For downcasting, these can be used to override existing properties or set new ones. As a convenience, you can supply a single unnamed list instead of individual name-value pairs, which makes it easy to override properties programmatically.
Examples
Foo1 := new_class(properties = list(x = class_integer))
Foo2 := new_class(Foo1, properties = list(y = class_double))
# Upcasting: S7 provides a default implementation for coercing an object
# to one of its parent classes:
convert(Foo2(x = 1L, y = 2), to = Foo1)
#> <Foo1>
#> @ x: int 1
# Downcasting: S7 also provides a default implementation for coercing an
# object to one of its child classes:
convert(Foo1(x = 1L), to = Foo2)
#> <Foo2>
#> @ x: int 1
#> @ y: num(0)
convert(Foo1(x = 1L), to = Foo2, y = 2.5) # Set new property
#> <Foo2>
#> @ x: int 1
#> @ y: num 2.5
convert(Foo1(x = 1L), to = Foo2, x = 2L, y = 2.5) # Override existing and set new
#> <Foo2>
#> @ x: int 2
#> @ y: num 2.5
# Converting to a base type falls back to the corresponding `as.*()`:
convert(1.5, to = class_character)
#> [1] "1.5"
convert(c("1", "2"), to = class_integer)
#> [1] 1 2
# For all other cases, you'll need to provide your own.
try(convert(Foo1(x = 1L), to = class_integer))
#> Error in as.integer(from, ...) :
#> cannot coerce type 'object' to vector of type 'integer'
method(convert, list(Foo1, class_integer)) <- function(from, to) {
from@x
}
convert(Foo1(x = 1L), to = class_integer)
#> [1] 1
# Conversion does not respect inheritance for `to`, so if we define a
# convert method for integer to Foo1
method(convert, list(class_integer, Foo1)) <- function(from, to) {
Foo1(x = from)
}
convert(1L, to = Foo1)
#> <Foo1>
#> @ x: int 1
# Converting to Foo2 will still error
try(convert(1L, to = Foo2))
#> Error in convert(1L, to = Foo2) :
#> Can't find method with dispatch classes:
#> - from: <integer>
#> - to : <Foo2>
# This is probably not surprising because Foo2 also needs some value
# for `@y`, but it definitely makes dispatch for convert() special
# Conversely, `convert()` *does* use inheritance for `from`, so a method
# registered on a parent class is also used for its children. This holds
# even when upcasting, where it overrides the default property stripping:
Bar1 := new_class(properties = list(label = class_character))
Bar2 := new_class(Bar1)
Bar3 := new_class(Bar2)
method(convert, list(Bar2, Bar1)) <- function(from, to, ...) {
Bar1(label = "from a Bar2 or one of its children")
}
convert(Bar2(), to = Bar1)
#> <Bar1>
#> @ label: chr "from a Bar2 or one of its children"
convert(Bar3(), to = Bar1) # Bar3 inherits Bar2, so the Bar2 method is used
#> <Bar1>
#> @ label: chr "from a Bar2 or one of its children"
# This `from`-inheritance is limited to classes more specific than `to`. A
# method whose `from` is a *parent* of `to` would downcast, so it is skipped.
# For example, this method downcasts a Foo1 to a Foo2:
Foo3 := new_class(Foo2, properties = list(z = class_double))
method(convert, list(Foo1, Foo2)) <- function(from, to, ...) Foo2(y = -1)
# Upcasting a Foo3 to a Foo2 ignores that inherited downcasting method,
# keeping `x` and `y` and dropping `z`, rather than resetting `y` to -1:
convert(Foo3(x = 1L, y = 2, z = 3), to = Foo2)
#> <Foo2>
#> @ x: int 1
#> @ y: num 2