---
title: "Extending constructive"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Extending constructive}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(constructive)
```

We detail in this vignette how {constructive} works and how
you might define custom constructors or custom `.cstr_construct.*()` methods.

This documents provides the general theory here but you are encouraged to look at examples.

In particular the package {constructive.examples} accessible at https://github.com/cynkra/constructive.example/
contains 2 examples, support a new class ("qr"), or implement a new constructor for
an already supported class ("tbl_df). This package might be used as a template.

The scripts starting with "s3-" and "s4-" in the {constructive} package provide
many more examples in a similar but slightly different shape, those 2
resources along with the explanations in this document should get you started.
Don't hesitate to open issues if things are unclear.

The next 5 sections describe the inner logic of the package, the last 2 sections
explain how to support a new class and/or define your own constructors.

The package is young and subject to breaking changes, so we apologize in advance
for the possible API breaking changes in the future.

## Recursion system

* `.cstr_construct()` builds code recursively, without checking input or output validity, 
  without handling errors, and without formatting.
* `construct()` wraps `.cstr_construct()` and does this extra work.
* `.cstr_construct()` is a generic and many methods are implemented in the package, for
  instance `construct(iris)` will call `.cstr_construct.data.frame()` which down the line
  will call `.cstr_construct.atomic()` and `.cstr_construct.factor()` to construct its columns.
* Additionally, before dispatching, `.cstr_construct()` attempts to match its data input
  to a list of objects provided to the `data` argument.
  
```{r}
.cstr_construct
.cstr_construct(letters)
construct(letters)
```


## `.cstr_construct.?()` methods

`.cstr_construct.?()` methods typically have this form:

```{r}
.cstr_construct.Date <- function(x, ...) {
  opts <- .cstr_fetch_opts("Date", ...)
  if (is_corrupted_Date(x) || opts$constructor == "next") return(NextMethod())
  constructor <- constructors$Date[[opts$constructor]]
  constructor(x, ..., origin = opts$origin)
}
```

* `.cstr_fetch_opts()` gathers options provided to `construct()` through the `opts_*()` function
  (see next section), or falls back to a default value if none were provided.
* If the object is corrupted or if the user decided to bypass the current method by
  choosing "next" as a constructor for this class, we return `NextMethod()` to
  forward all our inputs to a lower level constructor.
* `constructor()` actually builds the code from the object `x`, the parameters forwarded
  through `...` and the optional construction details gathered in `opts` (here the `origin`)

## `opts_?()` function

When implementing a new method you'll need to define and export the corresponding
`opts_?()` function. It provides to the user a way to choose a constructor and 
object retrieved by `.cstr_fetch_opts()` in the `.cstr_construct()` method.

It should always have this form:

```{r, eval=FALSE}
opts_Date <- function(
    constructor = c(
      "as.Date", "as_date", "date", "new_date",  "as.Date.numeric", 
      "as_date.numeric", "next", "atomic"
    ),
    ..., 
    origin = "1970-01-01"
  ) {
  .cstr_combine_errors(
    constructor <- .cstr_match_constructor(constructor),
    rlang::check_dots_empty()
  )
  .cstr_options("Date", constructor = constructor, origin = origin)
}
```

* The class is present in the name of the `opts_?()` function and as the first
  argument of `.cstr_options()`.
* A character vector of constructors is provided, starting with the default constructor
* A "next" value is mandatory except for internal types.
* Additional arguments passed to the constructors might be added, as we did here with
  `origin`
 
The following code illustrates how the information is retrieved.

```{r}
# .cstr_fetch_opts() takes a class and the dots and retrieves the relevant options
# if none were provided it falls back on the default value for the relevant opts_?() function
test <- function(...) {
  .cstr_fetch_opts("Date", ...)
}
test(opts_Date("as_date"), opts_data.frame("read.table"))
test()
```

## `is_corrupted_?()` function

`is_corrupted_?()` checks if `x` has the right internal type and attributes,
sometimes structure, so that it satisfies the expectations of a well formatted object
of a given class.

If an object is corrupted for a given class we cannot use constructors for this
class, so we move on to a lower level constructor by calling `NextMethod()` in
`.cstr_construct()`.

This is important so that `{constructive}` doesn't choke on corrupted objects but
instead helps us understand them.

For instance in the following example `x` prints like a date but it's corrupted,
a date should not be built on top of characters and this object cannot be built
with `as.Date()` or other idiomatic date constructors.

```{r, error = TRUE}
x <- structure("12345", class = "Date")
x
x + 1
```

We have defined : 

```{r}
is_corrupted_Date <- function(x) {
  !is.double(x)
}
```

And as a consequence the next method, `.cstr_construct.default()` will be called
through `NextMethod()` and will handle the object using an atomic vector constructor:

```{r}
construct(x)
```

## constructors

{constructive} exports a `constructors` environment object, itself containing
environments named like classes, the latter contain the constructor functions.

It is retrieved in the `.cstr_construct()` method by:

```{r, eval = FALSE}
constructor <- constructors$Date[[opts$constructor]]
```

For instance the default constructor for "Date" is :

```{r}
constructors$Date$as.Date
```

A function call is made of a function and its arguments. A constructor sets the
function and constructs its arguments recursively. 
This is done with the help of `.cstr_apply()` once these output have been prepared.
In the case above we have 2 logical paths because dates can be infinite but date vectors
containing infinite elements cannot be represented by `as.Date(<character>)`, our
preferred choice.

```{r}
x <- structure(c(12345, 20000), class = "Date")
y <- structure(c(12345, Inf), class = "Date")
constructors$Date$as.Date(x)
constructors$Date$as.Date(y)
```

It's important to consider corner cases when defining a constructor,
if some cases can't be handled by the constructor we should fall back to another
constructor or to another `.cstr_construct()` method.

For instance `constructors$data.frame$read.table()` falls back on 
`constructors$data.frame$data.frame()`  when the input contains non atomic columns,
which cannot be represented in a table input, and `constructors$data.frame$data.frame()`
itself falls back on `.cstr_construct.list()` when the data frame contains list columns not defined using `I()`, since `data.frame()` cannot produce such objects.

That last line of the function does the attribute reparation.

## Attribute reparation

Constructors should always end by a call to `.cstr_repair_attributes()` or a function
that wraps it.

These are needed to adjust the attributes of an object after idiomatic constructors such as `as.Date()` have defined their data and canonical attributes.

```{r}
x <- structure(c(12345, 20000), class = "Date", some_attr = 42)
# attributes are not visible due to "Date"'s printing method
x

# but constructive retrieves them
constructors$Date$as.Date(x)
```

`.cstr_repair_attributes()` essentially sets attributes with exceptions :

* It doesn't set names, these should be handled by the constructors
* It doesn't set the class explicitly if it's identical to the idiomatic class, i.e. the
  class returned by the constructor before the repair call, and provided through the
  `idiomatic_class` argument
* It doesn't set attributes that we choose to ignore because they are set by the
  constructor (e.g. row names for data frames or levels for factors)

`.cstr_repair_attributes()` does a bit more but we don't need to dive deeper in this vignette.


```{r}
constructive:::repair_attributes_Date

constructive:::repair_attributes_factor
```

## Register a new class

Registering a new class is done by defining and registering a `.cstr_construct.?()` method.
In a package you might register the method with {roxygen2} by using the "@export tag" 

## Register new constructors

You should not attempt to modify manually the `constructors` object of the {constructive} package, instead you should :

* Define an unexported constructor function 
* Call `.cstr_register_constructors(class_name, constructor_name = constructor_function, ...)` 

Do the latter in `.onload()` if the new constructor is to be part of a package,
for instance.


```{r, eval = FALSE}
# in zzz.R
.onLoad <- function(libname, pkgname) {
  .cstr_register_constructors(
    class_name, 
    constructor_name1 = constructor1, 
    constructor_name2 = constructor2
  )
}
```
