4 The type factory
The types that you wish to control are managed by a type factory. Retrieveing an object of this class, using function retrieveFactory, allows you to
- discover what are the already recorded types, available for reuse
- register your own types so they can be checked dynamically wherever and whenever required
- understand the verification logic for each type
4.1 Get recorded types inventory
Simply use getRecordedTypes function. It returns a data.table, that you can filter out conveniently.
Here is an example.
f <- retrieveFactory()
f$getRecordedTypes()
#> suffix type verify_function category
#> 1: a array <function> data structure
#> 2: b boolean <function> math
#> 3: c complex <function> numeric
#> 4: ca call <function> language
#> 5: ch character <function> basic
#> 6: cm complex-math <function> math
#> 7: d double <function> numeric
#> 8: da date <function> date
#> 9: dc POSIXct <function> date
#> 10: df data.frame <function> data structure
#> 11: dl POSIXlt <function> date
#> 12: dm double-math <function> math
#> 13: dt data.table <function> data structure
#> 14: e environment <function> basic
#> 15: er error <function> error management
#> 16: ex expression <function> language
#> 17: f function <function> basic
#> 18: fa factor <function> basic
#> 19: i integer <function> numeric
#> 20: im integer-math <function> math
#> 21: l list <function> data structure
#> 22: lo logical <function> basic
#> 23: m matrix <function> data structure
#> 24: n numeric <function> numeric
#> 25: na na <function> basic
#> 26: ni negative integer <function> math
#> 27: nm name <function> language
#> 28: nr negative real <function> math
#> 29: o object <function> basic
#> 30: pi positive integer <function> math
#> 31: pr positive real <function> math
#> 32: r real-math <function> math
#> 33: ra raw <function> basic
#> 34: rm real-math alias <function> math
#> 35: s string <function> basic
#> 36: sni strictly negative integer <function> math
#> 37: snr strictly negative real <function> math
#> 38: spi strictly positive integer <function> math
#> 39: spr strictly positive real <function> math
#> 40: t table <function> data structure
#> 41: ui unsigned integer <function> math
#> 42: ur unsigned real <function> math
#> 43: w warning <function> error management
#> suffix type verify_function category
4.2 Understanding the model
A type is defined by three elements
- a unique suffix
- a unique name
- a function that returns a boolean value, TRUE when examining a value that matches the type
For example, you might wonder what means ui as a suffix
f$getRecordedTypes()[suffix == 'ui']
#> suffix type verify_function category
#> 1: ui unsigned integer <function> math
What is the function related to suffix ui?
4.3 Register your own type
Type registration is achieved by providing a type suffix, a type name and a verification function. Registration will be tagged automatically as user-defined. Here is a typical sequence to register your own type
f$addSuffix('mc', 'MyClass', function(o_1l_) is(o_1l_, 'MyClass'))
#> [1] TRUE
f$getRecordedTypes()[suffix == 'mc']
#> suffix type verify_function category
#> 1: mc MyClass <function> user defined
Note that no implementation of the class is required. It is purely declarative registration. You told the type factory to record the type MyClass under the suffix mc, with the verification function you provided. Here verification function is quite simple. Notice that it takes a single object as argument and that function signature must match for the operation to succeed. Always bear great attention to returned value. It must be TRUE for your type to be registered into the factory.
4.4 Get access to verification functions
Implementation of verification function can range from quite simple to as complex as required. This allows you to manage functional scopes much more easily, whatever you work and organization context.
For example, if you want to see at a glance the differences between a boolean and a logical, here is the sequence you could use
f$getVerificationFunction('b')
#> function(o_1l_) {
#> if (!is.logical(o_1l_)) return(FALSE)
#> if (length(o_1l_) == 0) return(TRUE)
#> all(is.na(o_1l_) == FALSE)
#> }
#> <bytecode: 0x557d9bd10e50>
#> <environment: 0x557d9bdb37d8>
f$getVerificationFunction('logical')
#> function (x) .Primitive("is.logical")
From the two definitions, you can deduce the differences between the two types. A boolean is a 2-value boolean, either TRUE or FALSE. A logical, is a R logical value, that is a 3-value boolean, so it may take value NA.
Note that arguments to the function getVerificationFunction can be either a registered suffix or a registered type, as shown on this example.
4.5 Some hints
There is currently no way to remove one recorded type. This need is indeed very specific and arise only when there is a name collision and you wish to use an already taken name for your own purpose.
Solution is quite simple, use another name. Provided types are the most commons, and the current suffixes have been chosen for ease of use and for intuitive usage.
Whenever you need to register a new type, ask yourself ‘what is the suffix I wish to use for the new type?’. My advice is to use short suffixes, made of 2 or 3 letters. That’s clearly sufficient to distinguish your type from others. Know that there is not limitation to the length of the suffix you can use. Simply, comply with KISS, as you will have to type it several times, probably.
If you come from another programming language, you may consider to create aliased types by recording new entries. Let’s look at a concrete case.
f$addSuffix('ui', 'unsigned integer',
function(o_1_) f$getVerificationFunction('i')(o_1l_) && o_1_ >= 0L)
#> [1] FALSE
f$addSuffix('ul', 'unsigned long' , f$getVerificationFunction('ui'))
#> [1] TRUE
f$getRecordedTypes()[suffix %in% c('ui', 'ul')]
#> suffix type verify_function category
#> 1: ui unsigned integer <function> math
#> 2: ul unsigned long <function> user defined
You asked to add two new entries in the type factory, and they share the same verification function. First add fails, second one succeeds. The reason is that ui suffix is already defined and you cannot redefine an already defined suffix.
Now, ui and ul are aliases of the same verification function. Notice that this is true now, and cannot be changed once created. Indeed, you always have the opportunity to create a new type factory to match your new need. You can use as many type factories as you want.
The term alias shall not be understood, as an authorization to use one name for the other, but rather as an ability to define quickly new types to ease functional scope management.
4.6 Enforcing use of your own type factory
When you customized your own type factory, you need a way to tell wyz.code.offensiveProgramming to use it. To do so, simply create your type factory and assign it to a R variable, and set option op_type_factory to point to the name of this R variable. Let’s see an example
ff <- retrieveFactory()
ff$addSuffix('wo', "wo class", function(o_1l_) is(o_1l_, "wo"))
#> [1] TRUE
ff$addSuffix('yo', "yo class", function(o_1l_) is(o_1l_, "yo"))
#> [1] TRUE
ff$addSuffix('zo', "zo class", function(o_1l_) is(o_1l_, "zo"))
#> [1] TRUE
options(op_type_factory = ff)
fg <- retrieveFactory() # retrieves the factory pointed by R variable ff
fg$getRecordedTypes()[suffix %in% c('wo', 'yo', 'zo')] # right behavior !
#> suffix type verify_function category
#> 1: wo wo class <function> user defined
#> 2: yo yo class <function> user defined
#> 3: zo zo class <function> user defined
# wrong behavior as retrieveFactory will provide the default factory and not yours!
options(op_type_factory = "")
fh <- retrieveFactory() # retrieves the default factory
fh$getRecordedTypes()[suffix %in% c('wo', 'yo', 'zo')]
#> Empty data.table (0 rows and 4 cols): suffix,type,verify_function,category