This is the multi-page printable view of this section. Click here to print.
Implementation
- 1: Paradigms
- 2: Modularity
- 3: Syntax
1 - Paradigms
1.1 - Why ready4 is object oriented
This below section renders a vignette article from the ready4 library. You can use the following links to:
- view the vignette on the library website (adds useful hyperlinks to code blocks)
- view the source file from that article, and;
- edit its contents (requires a GitHub account).
Motivation
The practical utility and ease of use of computational models of mental health systems are in part shaped by the choice of programming paradigm used to develop them. ready4 adopts an object oriented programming (OOP) paradigm which in practice means that the framework principally consists of classes (representations of data structures useful for modelling mental health systems) and methods (algorithms that can be applied to these data-structures to generate insight useful for policy-making). Adopting an OOP approach is particular useful for making the ready4 model both modular and transparent.
Implementation
Modular Computational Models
Two commonly noted features of OOP - encapsulation and inheritance are particularly useful when developing modular computational models.
Encapsulation
Encapsulation allows us to define the data structures (“classes”) used in computational modelling projects in a manner that allows them to be safely combined. For example, assume there are two computational models, one (A) focused on predicting the types and intensity of services used by individuals that present to mental health services and the other (B) that predicts outcomes for recipients of these services. It may be desirable to develop a new model (C) that combines A and B to model both service use and outcomes. Using encapsulated code allows all of the features and functionality of A can be made available to B in a manner that protects the integrity of A. Specifically, B can only interact with A using the algorithms (“methods”) that have been already defined for A.
Furthermore, if appropriately implemented, methods associated with a class will work with any combination of input values that can be encapsulated by that class - making computational models more generalisabe. For example, imagine a class (X) that is used to structure summary data relevant to mental health systems. Methods associated with X (e.g. a method to derive an unmet need statistic) can then applied to two instances of X - one containing data relevant to the Australian context and one with data from the UK context.
Inheritence
The examples highlighted in the previous section have some potential limitations. What if the developers of A didn’t define methods that would allow B to interact with it in the desired way? Or what if there are a number of differences between the Australian and UK system that need to be accounted for when generalising a method from the former to the latter? These types of issues can be addressed by another key feature of OOP - inheritance. Inheritance allows for a “child” class to be created from a “parent” class. By default, the “child” inherits all of the features of the “parent” including all methods associated with the “parent” class. Importantly however, alternative or additional features can also be specified for the “child” to allow it to implement different methods where necessary. For example, when developing our new computational model C we could create a number of new classes that are children of the classes defined in A. We can then define any additional/alternative methods for these classes that overcome any integration issues between the classes and methods of A and B. In this way, we can enjoy the best of both worlds - leveraging all relevant algorithms from A and B (as there is no need to re-invent the wheel), while ensuring that we transparently develop the additional code required for C. This approach also ensures that the respective contributions of the (potentially different) authorship teams behind A, B and C is clearer.
Similarly, inheritance would allow re-use of much of the code from a model of the Australian mental health system when exploring similar topics within the UK context, while making it straightforward to develop additional code that addresses relevant divergence in features between the two jurisdictions. In practical terms, this would mean developing two child classes of X - class Y for use with Australian data and class Z for use in the UK system. All methods that are not specific to a particular jurisdiction are defined for X and inherited by Y and Z. Methods that are only appropriate for use in the Australian context are defined for Y, while UK specific methods are defined for Z.
Transparent Computational Models
To make analyses implemented using the ready4 model more readily understood, the ready4
package provides the model’s simple and consistent syntax. Such simplified approaches are facilitated by two other commonly noted features of OOP - polymorphism and abstraction.
Polymorphism
Polymorphism allows for similar concepts to be represented using consistent syntax. The same top level code can therefore be generalised to multiple model implementations, making algorithms simpler to understand and easier to re-use.
Returning to a previous example, the exact same command (e.g. a call to the method exhibit) can be applied to both Y (used for Australian data) and Z (used for UK data). However, the algorithm implemented by that command can vary based on the class that each method is applied to (ie a different algorithm is applied when the data is specified as being from the UK compared to being specified as Australian).
Abstraction
The simplicity enabled by polymorphism is enhanced by Abstraction, which basically means that only the briefest and easiest to comprehend parts of the code are exposed by default to potential users. Once an instance of a class is created, the entire program to ingest model data, analyse it and produce a scientific summary can be represented in a few brief lines of code, readily comprehensible to non-coders. When using open source languages, the elegance and simplicity of abstraction does not restrict the ability of more technically minded users exploring the detailed workings of the underpinning code.
1.2 - The role of functional programming in ready4 development
Although the object-oriented programming (OOP) approach ready4 implements has many advantages, it can also have some limitations. Some of these limitations have been colorfully highlighted by a popular quote attributed to Joe Armstrong:
“The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”
In practical terms, this means that if not carefully planned, using OOP can create barriers to code-reuse as algorithms come bundled with artefacts of no/low relevance to many potential users. To help maximise the accessibility and re-usability of ready4 algorithms, these algorithms are primarily written using the functional programming paradigm. Only once an algorithm has been implemented using functions are they then linked to a data-structure by means of a calling method. The typical development workflow for a ready4 computational modelling project might therefore look something the following three step process:
-
A modelling study algorithm is implemented as a program.
-
To help transfer the methods used in the study algorithm, it is decomposed into functions, which are bundled as a code library (or libraries). The program is updated to use the newly authored functions.
-
A ready4 module is authored to define a data-structure along with a method (or methods) that call the functions to implement the transferable version of the study algorithm. The new module is added to the previously created code library and the program is again updated so that the algorithm is now implemented by supplying data to the ready4 module and then calling the desired method(s).
Modellers using ready4 for the most part will only use ready4 modules and will rarely interact directly with the functions that implement module methods. However, these functions are potentially of significant usefulness to coders authoring new algorithms. A helpful way of exploring currently available functions is to use the ready4 dependencies app. All ready4 functions are created with minimal, but consistent documentation with the aid of tools from the ready4fun library.
2 - Modularity
This below section renders a vignette article from the ready4 library. You can use the following links to:
- view the vignette on the library website (adds useful hyperlinks to code blocks)
- view the source file from that article, and;
- edit its contents (requires a GitHub account).
Motivation
A potentially attractive approach to modelling complex youth mental health systems is to begin with a relatively simple computational model and to progressively extend its scope and sophistication. Such an approach could be described as “modular” if it is possible to readily combine multiple discrete modelling projects (potentially developed by different modelling teams) that each independently describe distinct aspects of the system being modelled. This modular and collaborative approach is being used in the development of ready4 - an open source health economic model of the systems shaping mental health and wellbeing in young people. The ready4
package provides the foundational tools to support the development and application of the ready4 modular model.
Implementation
The ready4 model is being implemented in R and its modular nature is enabled by the encapsulation and inheritance features of Object Oriented Programming (OOP). Specifically, ready4 uses two of R’s systems for implementing OOP - S3 and S4. An in-depth explanation of R’s different class system is beyond the scope of this article, but is explored in Hadley Wickham’s Advanced R handbook. However, it is useful to know some very high level information about S3 and S4 classes:
-
S4 classes are frequently said to be “formal”, “strict” or “rigorous”. The elements of an S4 class are called slots and the type of data that each slot is allowed to contain is specified in the class definition. An S4 class can be comprised of slots that contain different types of data (e.g. a slot that contains a character vector and another slot that contains tabular data).
-
S3 classes are often described as “simple”, “informal” and “flexible”. S3 objects attach an attribute label to base type objects (e.g. a character vector, a data.frame, a list), which in turn is used to work out what methods should be applied to the class.
ready4 Model Modules
A ready4 model module is a data-structure and associated algorithms that is used to model a discrete component of a system relevant to young people’s mental health. Each ready4 model module is created using the ready4
package’s Ready4Module
class. We can create an instance (X
) of Ready4Module
using the following command.
X <- ready4::Ready4Module()
However, if we inspect X
we can see it is of limited use as it contains no data other than an empty element called dissemination_1L_chr
.
str(X)
#> Formal class 'Ready4Module' [package "ready4"] with 1 slot
#> ..@ dissemination_1L_chr: chr NA
The Ready4Module
class is therefore not intended to be called directly. Instead, the purpose of Ready4Module
is to be the parent-class of all ready4 model modules. Ready4Module
and all of its child-classes (ie all ready4 model modules) are “S4” classes.
ready4 Concept
Module
A formal (S4) Ready4Module
child-class and its associated methods used to implement a discrete sub-component of the ready4 youth mental health model.
ready4
includes two child classes of Ready4Module
. These are Ready4Public
and Ready4Private
and both are almost as minimally informative as their parent (the only difference being that their instances have the values “Public” or “Private” assigned to the dissemination_1L_chr
slot).
Y <- Ready4Public()
str(Y)
#> Formal class 'Ready4Public' [package "ready4"] with 1 slot
#> ..@ dissemination_1L_chr: chr "Public"
Z <- Ready4Private()
str(Z)
#> Formal class 'Ready4Private' [package "ready4"] with 1 slot
#> ..@ dissemination_1L_chr: chr "Private"
Like the Ready4Module
class they inherit from, the purpose of Ready4Public
and Ready4Private
is to be used as parent classes. Using either of Ready4Public
and Ready4Private
can be a potentially efficient way of partially automating access policies for model data. If all the data contained in a module can always be shared publicly, it may be convenient to note this by using a module that has been created as a child-class of Ready4Public
. Similarly, if at least some of the data contained in a module will always be unsuitable for public dissemination, it can be useful to use a module that is a child of Ready4Private
. When the dissemination policy for data contained in a module will vary depending on user or context, it is more appropriate to use a module that inherits from Ready4Module
without being a child of either Ready4Public
and Ready4Private
. In this latest case, users may choose to add descriptive information about the data access policy themselves using the renewSlot
method. The dissemination policy can be inspected with the procureSlot
method.
X <- renewSlot(X,
"dissemination_1L_chr",
"Staff and students of research institutes")
procureSlot(X,
"dissemination_1L_chr")
#> [1] "Staff and students of research institutes"
ready4 Model Sub-modules
In ready4, S3 classes are principally used to help define the structural properties of slots (array elements) of model modules and the methods that can be applied to these slots. S3 classes created for these purposes are called sub-modules.
ready4 Concept
Sub-Module
An informal (S3) class and its associated methods that describes, validates and applies algorithms to a slot of a ready4 module.
Module and Sub-module Methods
All methods associated with ready4 modules and sub-modules adopt a common syntax. However, the algorithms implemented by each command in that syntax will vary depending on which module it is applied to. A limited number of methods are defined at the level of the Ready4Module
parent class and are therefore inherited by all ready4 modules. Currently, the only methods defined for Ready4Module
are slot-methods and these can be itemised using the get_methods
function.
get_methods()
#> [1] "authorSlot" "characterizeSlot" "depictSlot" "enhanceSlot" "exhibitSlot"
#> [6] "ingestSlot" "investigateSlot" "manufactureSlot" "metamorphoseSlot" "procureSlot"
#> [11] "prognosticateSlot" "ratifySlot" "reckonSlot" "renewSlot" "shareSlot"
3 - Syntax
This below section renders a vignette article from the ready4 library. You can use the following links to:
- view the vignette on the library website (adds useful hyperlinks to code blocks)
- view the source file from that article, and;
- edit its contents (requires a GitHub account).
Motivation
Transparency is one of the underpinning principles of open science. One way to improve the transparency of the ready4 model is to ensure that the programs implementing analyses using this model can be meaningfully inspected by readers with different levels of technical expertise. Even non-technical readers should be able to follow the high-level logic implemented by model algorithms. By using a simple programming syntax that can be consistently used across all model analyses programs, ready4 can help ensure that readers need to contend with relatively few new concepts when reviewing analysis code.
Implementation
ready4
provides a simple syntax that can be consistently applied to all ready4 model modules. It does so by taking advantage of the polymorphism and abstraction features of Object Oriented Programing and R’s use of generic functions. Generic functions don’t obviously do anything by themselves - their most salient features are a name and a high level description of the type of task that any method using that name should perform. Whenever a method is defined for classes that use R’s S4 and S3 systems (the types used for ready4 model modules and sub-modules), it is assigned to the generic that is the best match for the algorithm it implements.
Finding ready4 Methods
A table that summarises the syntax used by ready4 model module methods, can be generated by web-scraping using make_methods_tb
(which produces up to date results but can be a little slow to excecute) or alternatively be downloaded from a periodically updated database using get_methods_tb
(which is quicker to implement, but may miss the most recent additions).
# Not run
# x <- make_methods_tb()
x <- get_methods_tb()
Core generics
ready4
includes a number of core generic functions which describe the main types of method to be implemented by ready4 model modules. Notably, the ready4
package does not define methods for any of these core generics. Instead, methods are defined for these generics in R packages that contain ready4 modules. A HTML table of the core generics bundled with ready4
and examples of methods that implement each generic can be displayed using the print_methods
function, using the return_1L_chr = "core"
argument.
print_methods(x,
return_1L_chr = "core",
scroll_width_1L_chr = "100%")
Method | Purpose | Examples |
---|---|---|
author | Author and save files | 5 , 6 |
characterize | Characterize data by generating (tabular) descriptive statistics | |
depict | Depict (plot) features of a dataset | 2, 3 , 4 |
enhance | Enhance a dataset by adding new elements | |
exhibit | Exhibit features of a dataset by printing them to the R console | 2, 3 , 4 , 6 |
ingest | Ingest data | 2, 3 , 4 , 5 , 6 |
investigate | Investigate solutions to an inverse problem | 6 |
manufacture | Manufacture a new object | |
metamorphose | Metamorphose data from one model module (or sub-module) instance to an instance of a different model module or sub-module | 5 , 6 |
procure | Procure items from a dataset | 6 |
prognosticate | Prognosticate (make predictions) by solving a forward problem | |
ratify | Ratify that a dataset meets validity criteria | 2, 6 |
reckon | Reckon (calculate) a value | |
renew | Renew values in a dataset | 2, 3 , 4 , 5 , 6 |
share | Share data via an online repository | 2, 3 , 4 |
Slot generics and methods
Each of the “core” generics also has a “slot” version, for use when applying a core method to a specified slot of a class. The ready4
package defines methods for each of these “slot” generics for the Ready4Module
class. Two of these “slot” methods can also be used for additional purposes:
-
procureSlot is a “getter” method - its default behaviour is to return the value of a specified slot. If the argument
use_procure_mthd_1L_lgl = T
is included in the method call,procureSlot
will instead apply theprocure
method to a specified slot. -
renewSlot is a “setter” method - if any value other than “use_renew_mthd” (the default) is passed to the
new_val_xx
argument, that value will be assigned to the specified slot.
A HTML table of the slot generics bundled with ready4
can be displayed using the print_methods
function, using the return_1L_chr = "slot"
argument.
print_methods(x,
return_1L_chr = "slot",
scroll_width_1L_chr = "100%")
Method | Purpose | Examples |
---|---|---|
authorSlot | Apply the author method to a model module slot | 5 |
characterizeSlot | Apply the characterize method to a model module slot | |
depictSlot | Apply the depict method to a model module slot | |
enhanceSlot | Apply the enhance method to a model module slot | 5 |
exhibitSlot | Apply the exhibit method to a model module slot | 5 , 6 |
ingestSlot | Apply the ingest method to a model module slot | |
investigateSlot | Apply the investigate method to a model module slot | 5 |
manufactureSlot | Apply the manufacture method to a model module slot | 5 |
metamorphoseSlot | Apply the metamorphose method to a model module slot | 5 |
procureSlot | Procure (get) data from a slot | 3 , 5 , 6 |
prognosticateSlot | Apply the prognosticate method to a model module slot | |
ratifySlot | Apply the ratify method to a model module slot | 5 |
reckonSlot | Apply the reckon method to a model module slot | |
renewSlot | Apply the renew method to a model module slot | 2, 3 , 5 , 6 |
shareSlot | Apply the share method to a model module slot | 5 |
Extended author
generics
Finally, there are a small number of other generics that are more general extensions of the core functions. Currently, these extended generics are all variants on the author
generics, with each specifying the type of output to be authored by the method. The ready4
package does not include methods for any of these extended generics. A HTML table of the extended generics bundled with ready4
can be displayed using the print_methods
function, using the return_1L_chr = "extended"
argument.
print_methods(x,
exclude_mthds_for_chr = "Ready4Module",
return_1L_chr = "extended",
scroll_width_1L_chr = "100%")
Method | Purpose | Examples |
---|---|---|
authorClasses | Author and document classes | |
authorData | Author and document datasets | 5 , 6 |
authorFunctions | Author and document functions | |
authorReport | Author and save a report |