0

I'm very confused by what's going on in this example.

I'm trying to write a function factory and pass it a list of parameters to create a list of functions. I've had luck with this method for simple functions. Now I'm trying to do a more complex problem that uses a list of parameters. I was not able to get it to work with lapply, but I'm open to that approach. I tried a loop instead and things are behaving strangely.

library(TTR)
data(ttrc)
#last 100 closing prices for testing
ttrc <- tail(ttrc['Close'], 100)

#Function Factory for creating moving average functions
MAFactory <- function(fun, n) function(x){
  setNames(get(fun)(x = x, n = n), paste(fun, n, sep = "_"))
}
#parameters to use with the funciton factory
grid <- expand.grid(fncts = c("SMA", "EMA", "DEMA"),
                    ns = c(10, 20, 30),
                    stringsAsFactors = FALSE)

MAfuns <- vector("list", nrow(grid))

#loop over the grid to apply the function factory
for (i in 1:nrow(grid)) {
  #print statements for debugging
  print(i)
  MAfuns[[i]] <- MAFactory(grid[i, 1], grid[i, 2])
  print(paste(grid[i,], collapse = ''))
}

#test cases
sma10 <- MAFactory("SMA", 10)
sma10Simple <- SMA(x = ttrc, n = 10)

(sma10Simple == sma10(ttrc))
(MAfuns[[1]](ttrc) == sma10(ttrc))
(MAfuns[[1]](ttrc) == sma10Simple)

#cause of failure
tmpE <- environment(MAfuns[[1]])
mget(envir = tmpE, x = ls(envir = tmpE))
#all MAfuns use last parameters from grid???

#but it works outside the loop when i = 1?!
MAfuns[[1]] <- MAFactory(grid[1, 1], grid[1, 2])
(MAfuns[[1]](ttrc) == sma10(ttrc))

I think the issue has something to do with promise evaluation, based on this question. But the fix, calling the argument in the function factory, does not work for me. If for some reason this can't be done inside a loop, how could I rewrite it with lapply?

Community
  • 1
  • 1
brandco
  • 310
  • 2
  • 6
  • I believe this answer should be helpful to you: http://stackoverflow.com/a/30131673/2372064 – MrFlick Feb 29 '16 at 00:12
  • Your example is not reproducible. Where does `Cl` come from? You probably have to use `force` to enforce the evaluation of one of your arguments in the functions enclosing environments. I have some notes on this issue you can find here: https://wahani.github.io/2014/09/Promises-and-Closures-in-R/ – Sebastian Mar 22 '16 at 07:24
  • Sorry. Cl is from quantmod package. I edited the script so that you don't need it. – brandco Mar 23 '16 at 02:08
  • Nice blog post, thanks for the link. I used `force` to evaluate `n` in MAFactory and it does not work. I solved my practical problem the day after posting this by re-approaching with out for loops. But, I'm still not sure I understand lazy eval because I can't figure out why this example does not work. – brandco Mar 23 '16 at 02:25

2 Answers2

0

I came up with an answer to my own question. I don't know if it can shed light on what is going on withe closure/lazy eval/for loop issue, but it does solve the problem.

library(TTR)
data(ttrc)
#last 100 closing prices for testing
ttrc <- tail(ttrc['Close'], 100)

#Function Factory for creating moving average functions
MAFactory <- function(fun, n) function(x){
  get(fun)(x = x, n = n)
}
#parameters to use with the funciton factory
grid <- expand.grid(fncts = c("SMA", "EMA", "DEMA"),
                    ns = c(10, 20, 30),
                    stringsAsFactors = FALSE)

###__Answer__###
#create the functions in a list
MAfuns <- lapply(1:nrow(grid), function(i) get(grid[i,1]))
#name the functions
names(MAfuns) <- lapply(1:nrow(grid), function(i) paste(grid[i,],
                                                          collapse = ''))
#change the formals of each function in the list directly
for (i in 1:nrow(grid)) {formals(MAfuns[[i]])$n <- grid$ns[i]}
###

sma10 <- MAFactory("SMA", 10)
sma10Simple <- SMA(x = ttrc, n = 10)

(sma10Simple == sma10(ttrc))
(MAfuns[[1]](ttrc) == sma10(ttrc))

What I'm trying to do here is create a long list of functions with different arguments without having to copy and paste or repeat my code. Most of the examples of this kind pattern that I have seen online, including what's linked to in this question, involve functions with one argument. But what do you do if you have two arguments that you want to change? Or more than one functions with the same list of arguments?

This answer solves this problem by manipulating the function's formals directly and avoiding the parent environment and lazy evaluation issues all together.

brandco
  • 310
  • 2
  • 6
0

I still think that the problem you are facing is described in the post I referenced in the comment above. Your solutions appears to be more involved than necessary which is why I want to make a different suggestion. What we need as a solution is that the arguments to MAFactory are evaluated in the enclosing environment of the returned function. That's what the function force is for. It forces the early evaluation of a promise. Since you have a function with more than one argument (MAFactory) lapply is not doing the job - you need a multivariate map. In R the multivariate map is named mapply in correspondence to the *apply family of functions or Map. mapply will try to simplify it's results, which is something I always turn out of habit unless I consciously want it.

library(TTR)
data(ttrc)
#last 100 closing prices for testing
ttrc <- tail(ttrc['Close'], 100)

#Function Factory for creating moving average functions
MAFactory <- function(fun, n) {
    force(fun)
    force(n)
    function(x){
        get(fun)(x = x, n = n)
    }
}

MAfuns <- mapply(
    MAFactory, 
    c("SMA", "EMA", "DEMA"), 
    c(10, 20, 30), 
    SIMPLIFY = FALSE
)


#test cases
sma10 <- MAFactory("SMA", 10)
sma10Simple <- SMA(x = ttrc, n = 10)

identical(sma10Simple, sma10(ttrc))
identical(MAfuns[[1]](ttrc), sma10(ttrc))
identical(MAfuns[[1]](ttrc), sma10Simple)

In your initial example you want to use the function setNames which needs a vector of names of the same length as the first argument - something which is not necessarily the case. However it is unrelated - I think - to your real question.

Sebastian
  • 840
  • 6
  • 11