Search code examples
runits-of-measurementreference-class

R Reference Class: Field of type 'units'


I have a reference class, one of the fields for which is a 'units' object from the R units package. My initial class definition looked like this:

MyClass = 
    setRefClass(
        "MyClass",
        fields = list(units_value = "numeric")
    )

uv = units::as_units(10, 'cm')

mc = MyClass(units_value = uv)

Which threw the following error:

Error: invalid assignment for reference class field ‘units_value’, should be from class “numeric” or a subclass (was class “units”)

So I tried to set the field class to units like so:

MyClass = 
    setRefClass(
        "MyClass",
        fields = list(units_value = "units")
    )

uv = units::as_units(10, 'cm')

mc = MyClass(units_value = uv)

But this throws a whole new error:

Error in refClassInformation(Class, contains, fields, methods, where) : 
class “units” for field ‘units_value’ is not defined

For the moment I've settled for using a non-typed class constructor, but this seems like such a simple problem that it's hard for me to believe there's no way to construct a Reference Class with a units object as a field. Does anyone know how to accomplish this?


Solution

  • "units" is an S3 class, so doesn't have a formal class definition. To get an S3 class to be represented inside an S4 or reference class, you need to register it first as a formally defined class using setOldClass

    We can see that there is no formal class definition for "units" if we do:

    library(units)
    
    getClassDef("units")
    #> NULL
    

    But if we use setOldClass, we can see it becomes registered:

    setOldClass("units")
    
    getClassDef("units")
    #> Virtual Class "units" [in ".GlobalEnv"]
    #> 
    #> Slots:
    #>                 
    #> Name:   .S3Class
    #> Class: character
    #> 
    #> Extends: "oldClass"
    

    This now allows your second code block to work as expected:

    MyClass = 
      setRefClass(
        "MyClass",
        fields = list(units_value = "units")
      )
    
    uv = units::as_units(10, 'cm')
    
    mc = MyClass(units_value = uv)
    
    mc
    #> Reference class object of class "MyClass"
    #> Field "units_value":
    #> 10 [cm]
    

    The alternative is to set the class as "ANY" instead of "units", but this always feels like a last resort if you are trying to stick to OOP principles.

    Created on 2022-12-15 with reprex v2.0.2