Chapter 10 Function factories

10.2 Factory fundamentals

Let’s really solidify this statement in our own words using the power1 and square/cube example:

The enclosing environment of the manufactured function is an execution environment of the function factory.

  • enclosing environment (this is the function environment - like square and cube) [in red]
  • execution environment (this is the same environment as above but from the “perspective” of power1) [in green]

square and cube have their own enclosing environments where x is different for each function - but their parent, the environment of power1, is the same.

Because power1 is a function, it has its own execution environment which is usually ephemeral but in this case it is the environment of both square and cube – and that is how the manufactured functions have access to exp.

10.2.3 Forcing evaluation

I think this force section is trying to convey how we should use force on every argument in the factory’s execution environment (here just exp) because you want to avoid lazy eval in the manufactured function’s enclosing environment (like how we want to force(exp = 2) for square) but how does it not just become force(exp = 3) when we set x <- 3?

10.1 WITHOUT FORCE

Without force, exp doesn’t get evaluated when power is called. square is a promise to create a function to raise its input to the exp’th power. Once square is called, the promise evaluates, and square becomes a function to raise its input to whatever exp was that first time square was called. That’s what makes it dangerous; what square does depends on the order in which things are called. Dangers and confusions like this are part of why function factories don’t get used that much, despite the fact that they’re really quite cool.

#> outer execution environment: <environment: 0x0000000015812568>
#> outer enclosure: <environment: R_GlobalEnv>
#> exp promise info: 
#>  $code
#>  x
#>  
#>  $env
#>  <environment: R_GlobalEnv>
#>  
#>  $evaled
#>  [1] FALSE
#>  
#>  $value
#>  NULL
#>  
#> inner execution environment: <environment: 0x0000000012aee2a8>
#> inner enclosure: <environment: 0x0000000015812568>
#> exp promise info: 
#>  $code
#>  x
#>  
#>  $env
#>  <environment: R_GlobalEnv>
#>  
#>  $evaled
#>  [1] FALSE
#>  
#>  $value
#>  NULL
#>  
#> Error in square(4): object 'x' not found

The exp promise is looking for x in the enclosure of power2 (global) and can’t find it.

#> inner execution environment: <environment: 0x0000000017c915d0>
#> inner enclosure: <environment: 0x0000000015812568>
#> exp promise info: 
#>  $code
#>  x
#>  
#>  $env
#>  <environment: R_GlobalEnv>
#>  
#>  $evaled
#>  [1] FALSE
#>  
#>  $value
#>  NULL
#>  
#> Warning in square(4): restarting interrupted promise evaluation
#> [1] 16

Notice, that the exp promise still wasn’t evaled until the last line, x_ ^ exp. The warning is caused by our previous failed attempt to evaluate the promise.

#> inner execution environment: <environment: 0x0000000017d9b270>
#> inner enclosure: <environment: 0x0000000015812568>
#> exp promise info: 
#>  $code
#>  x
#>  
#>  $env
#>  NULL
#>  
#>  $evaled
#>  [1] TRUE
#>  
#>  $value
#>  [1] 2
#>  
#> [1] 25
# exp was evaled already, so no further warning is produced, and our change to x has no effect.
#> outer execution environment: <environment: 0x0000000017e96e78>
#> outer enclosure: <environment: R_GlobalEnv>
#> exp promise info: 
#>  $code
#>  x
#>  
#>  $env
#>  <environment: R_GlobalEnv>
#>  
#>  $evaled
#>  [1] FALSE
#>  
#>  $value
#>  NULL
#>  
#> inner execution environment: <environment: 0x0000000017f8a530>
#> inner enclosure: <environment: 0x0000000017e96e78>
#> exp promise info: 
#>  $code
#>  x
#>  
#>  $env
#>  <environment: R_GlobalEnv>
#>  
#>  $evaled
#>  [1] FALSE
#>  
#>  $value
#>  NULL
#>  
#> Error in square(6): object 'x' not found

Defining x in the function environment doesn’t work, because the promise isn’t looking for x there

[1] 36
Error in square(6): object 'x' not found

This doesn’t work because we need to define x within environment(power2)$exp <- 2

10.1.1 USING FORCE

#> outer execution environment: <environment: 0x00000000184e4e90>
#> outer enclosure: <environment: R_GlobalEnv>
#> exp promise info before: 
#>  $code
#>  x
#>  
#>  $env
#>  <environment: R_GlobalEnv>
#>  
#>  $evaled
#>  [1] FALSE
#>  
#>  $value
#>  NULL
#>  
#> Error in force(exp): object 'x' not found
# error because x isn't defined
#> outer execution environment: <environment: 0x0000000018cafaf8>
#> outer enclosure: <environment: R_GlobalEnv>
#> exp promise info before: 
#>  $code
#>  x
#>  
#>  $env
#>  <environment: R_GlobalEnv>
#>  
#>  $evaled
#>  [1] FALSE
#>  
#>  $value
#>  NULL
#>  exp promise info after: 
#>  $code
#>  x
#>  
#>  $env
#>  NULL
#>  
#>  $evaled
#>  [1] TRUE
#>  
#>  $value
#>  [1] 2
#>  
#> inner execution environment: <environment: 0x0000000018d55b48>
#> inner enclosure: <environment: 0x0000000018cafaf8>
#> exp promise info: 
#>  $code
#>  x
#>  
#>  $env
#>  NULL
#>  
#>  $evaled
#>  [1] TRUE
#>  
#>  $value
#>  [1] 2
#>  
#> [1] 16

10.2.5 garbage collection

Does {factory} handle the issue of temporary objects in the manufactured function not getting garbage collected?

We don’t need to use rm because the execution environment of the factory remains ephemeral (like any other function call).Rather than returning a function wrapped in the execution environment, it returns a function in the caller environment, allowing normal garbage collection to clean up the temporary objects created during creation of the manufactured functions.

10.2.6.2 Exercise

Base R contains two function factories, approxfun() and ecdf(). Read their documentation and experiment to figure out what the functions do and what they return.

approxfun

approxfun creates a function (i.e. mathematical formula) that can be used to find the linear interpolation for any given point, based on the data that was passed into the factory-function at the time it was passed into the function

##     x   y
## 1   1   1
## 2   2   4
## 3   3   9
## 4   4  16
## 5   5  25
## 6   6  36
## 7   7  49
## 8   8  64
## 9   9  81
## 10 10 100
## [1] 20.5

10.3.1 Labelling

What is the “factory” happening here? That we’re supplying a function as the label argument? Is summarize also a factory then?

It isn’t that the ggplot2 functions are factories, it’s that they’re a great place to use factories. They’re actually functionals when you pass in a function, since they’re using a function as an argument!

10.3.4 Exercises

Can we use code to answer the question: compare and contrast ggplot2::label_bquote() with scales::number_format()