- Notifications
You must be signed in to change notification settings - Fork6
factory: Build Function Factories
License
Unknown, MIT licenses found
Licenses found
jonthegeek/factory
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
The goal of factory is to make construction of function factories morestraightforward, without requiring the user to learn therlangpackage.
Install the released version of factory from CRAN:
install.packages("factory")Or install the development version fromGitHub with:
# install.packages("remotes")remotes::install_github("jonthegeek/factory")
Function factories are functions that make functions. They can beconfusing to work with. For example, as we’ll see below, they canproduce functions that are fragile, or that are confusing to work withas a user.
WARNING: All code shown below is “wrong” in some way until we get to theexample at the end! These examples show the dangers of working withfunction factories, and why this package exists.
(examples adapted fromAdvanced R by Hadley Wickham (2ndEdition), 10.2.3: ForcingEvaluation)
power1 is a function factory. It returns a function based on theexponent argument.
power1<-function(exponent) {function(x) {x^exponent }}
For many use cases,power1 works fine. For example, we can define asquare function by callingpower1 withexponent = 2.
square1<- power1(2)square1(2)#> [1] 4# 2 ^ 2 = 4square1(3)#> [1] 9# 3 ^ 2 = 9
However,power1 is fragile. Let’s think about what the definition ofpower1means. The function returned bypower1 raises its argument towhatever theexponent variable is defined as. Let’s see what happensif we use a variable in the global environment to define oursquarefunction.
my_exponent<-2square1a<- power1(my_exponent)
Due to R’s lazy evaluation, when we callpower1, theexponentvariable gets a promise to take on the value of themy_exponentvariable. Butmy_exponent doesn’t actually have the value of2 yet.Until weusemy_exponent, it has apromise to get the value of2. If we callsquare1a right away, it works as expected.
square1a(2)#> [1] 4# 2 ^ 2 = 4my_exponent<-3square1a(3)#> [1] 9# 3 ^ 2 = 9
Themy_exponent promise (which was passed in during the definition ofsquare1a) resolves to2 the first time it is needed (whensquare1ais first called). After that initial call, that is the value used insquare1a forever.
But ifmy_exponent changes between definition of our function andfirst call of that function, we get a different result.
my_exponent<-2square1b<- power1(my_exponent)my_exponent<-3square1b(2)#> [1] 8# 2 ^ 3 = 8square1b(3)#> [1] 27# 3 ^ 3 = 27
What happened? Whensquare1b was defined,my_exponent was passed inas apromise. However, beforemy_exponent was ever actuallyused,its value changed. The promise isn’t evaluateduntil it is used,which, in this case, is the first timesquare1b is called. Once thepromise is evaluated, its value is “fixed,” and the function works asexpected.
We can make factories that are less fragile, if we remember toforcethe variables.
power2<-function(exponent) { force(exponent)# Gah, easy to forget!function(x) {x^exponent }}my_exponent<-2square2<- power2(my_exponent)my_exponent<-3square2(2)#> [1] 4# 2 ^ 2 = 4square2(3)#> [1] 9# 3 ^ 2 = 9
Why does this work? Theforce function forces the evaluation of itsargument. We don’t really need to useforce, per se. Any function thatforces evaluation would work, butforce makes it obvious why we’redoing it. For example, we could produce the same result bymessageingwithin the factory.
power2b<-function(exponent) { message("The exponent's value is",exponent)function(x) {x^exponent }}my_exponent<-2square2b<- power2b(my_exponent)#> The exponent's value is 2my_exponent<-3square2b(2)#> [1] 4# 2 ^ 2 = 4square2b(3)#> [1] 9# 3 ^ 2 = 9
Since the value ofexponent is needed for the message, the promise isevaluated when the factory is invoked, and the resulting function isstable.
While such factories are more stable, it’s easy to miss aforce. And,in both of these cases, the resulting functions are difficult tounderstand as a user.
square1#> function(x) {#> x ^ exponent#> }#> <environment: 0x000000001499b588>square2#> function(x) {#> x ^ exponent#> }#> <environment: 0x000000001d0daba8>cube<- power2(3)cube#> function(x) {#> x ^ exponent#> }#> <bytecode: 0x000000001db4af70>#> <environment: 0x000000001da4d950>
It isn’t clear what these functions will do, since the definitions ofexponent are hidden inside the function environments.
We can use {rlang} to make functions that are easier to understand, butbuilding the function factory is much more difficult (fromAdvanced Rby Hadley Wickham (2nd Edition), 19.7.4: Creatingfunctions):
power3<-function(exponent) {rlang::new_function(rlang::exprs(x= ),rlang::expr({x^!!exponent }),rlang::caller_env() )}
The resulting functions look like a “normal” function, though, and arethus easier for users to understand.
square3<- power3(2)square3#> function (x)#> {#> x^2#> }
The {rlang} calls are very difficult to understand, though. It would benice to get the stability and interpretability of the rlang-producedfunctions, with the ease-of-programming of the simplest functionfactories.
The goal offactory is to make function factories as straightforwardto create as inpower1, but to make the resulting functions make asmuch sense as inpower3. Right now, the calls are still alittlemore complicated than I would like, but they’re definitely easier tounderstand than the {rlang} calls.
library(factory)power4<- build_factory(fun=function(x) {x^exponent },exponent)my_exponent<-2square4<- power4(my_exponent)my_exponent<-3square4(2)#> [1] 4# 2 ^ 2 = 4
The resulting function makes sense, as withpower3.
square4#> function (x)#> {#> x^2#> }
About
factory: Build Function Factories
Topics
Resources
License
Unknown, MIT licenses found
Licenses found
Code of conduct
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Contributors3
Uh oh!
There was an error while loading.Please reload this page.
