igorkf
igorkf

Reputation: 3565

Modify an argument of a function that is inside another function, without having to redefine the whole function again

Edit:

The labelFormat function is from the leaflet package.
I would like to modify an argument inside the format function that is inside the formatNum function.

So from this:

labelFormat <- function (prefix = "", suffix = "", between = " &ndash; ", digits = 3, 
    big.mark = ",", transform = identity) 
{
    formatNum <- function(x) {
        format(round(transform(x), digits), trim = TRUE, scientific = FALSE, 
            big.mark = big.mark)
    }
    function(type, ...) {
        switch(type, numeric = (function(cuts) {
            paste0(prefix, formatNum(cuts), suffix)
        })(...), bin = (function(cuts) {
            n <- length(cuts)
            paste0(prefix, formatNum(cuts[-n]), between, formatNum(cuts[-1]), 
                suffix)
        })(...), quantile = (function(cuts, p) {
            n <- length(cuts)
            p <- paste0(round(p * 100), "%")
            cuts <- paste0(formatNum(cuts[-n]), between, formatNum(cuts[-1]))
            paste0("<span title=\"", cuts, "\">", prefix, p[-n], 
                between, p[-1], suffix, "</span>")
        })(...), factor = (function(cuts) {
            paste0(prefix, as.character(transform(cuts)), suffix)
        })(...))
    }
}

I would like to have this:

labelFormat <- function (prefix = "", suffix = "", between = " &ndash; ", digits = 3, 
    big.mark = ",", transform = identity) 
{
    formatNum <- function(x) {
        format(round(transform(x), digits), trim = TRUE, scientific = FALSE, 
            big.mark = big.mark, decimal.mark = ',')
    }
    function(type, ...) {
        switch(type, numeric = (function(cuts) {
            paste0(prefix, formatNum(cuts), suffix)
        })(...), bin = (function(cuts) {
            n <- length(cuts)
            paste0(prefix, formatNum(cuts[-n]), between, formatNum(cuts[-1]), 
                suffix)
        })(...), quantile = (function(cuts, p) {
            n <- length(cuts)
            p <- paste0(round(p * 100), "%")
            cuts <- paste0(formatNum(cuts[-n]), between, formatNum(cuts[-1]))
            paste0("<span title=\"", cuts, "\">", prefix, p[-n], 
                between, p[-1], suffix, "</span>")
        })(...), factor = (function(cuts) {
            paste0(prefix, as.character(transform(cuts)), suffix)
        })(...))
    }
}

See the new argument decimal.mark inside the format function.
But to do this, I would not like to redefine the labelFormat function.

In Python there's something that I use quite similar to modify an argument inside a function. Let's suppose that we want to modify the default behaviour of json.dumps:

import functools
import json

my_json_dumps = functools.partial(json.dumps, ensure_ascii=False)

Now I can call my_json_dumps instead of using json.dumps, this way I don't needed to define the whole function again.

My problem is a little different, because I need to modify an argument that is inside an inner function!

Upvotes: 0

Views: 117

Answers (2)

MrFlick
MrFlick

Reputation: 206197

In this case labelFormat returns a function that contains a reference to the environment where formatNum is defined. Here we can create a wrapper to change that value

my_labelFormat <- function(...) {
   fun <- labelFormat(...)
   evalq(formatNum <- function(x) {
     format(round(transform(x), digits), trim = TRUE, scientific = FALSE, 
            big.mark = big.mark, decimal.mark = ',')
  }, environment(fun))
  return(fun)
}

So we call the default labelFormat function but them manipulate the environment to change the formatNum function

Upvotes: 4

Ben Bolker
Ben Bolker

Reputation: 226087

It's not easy. There's no way I know of to call g() from outside of f(), since that function does not exist until after you call f() and execute the first step. To see this, try the following:

debug(f)
f(1)
ls() ## shows 'number', the only object in the local environment
g()  ## Error: could not find function g
<ENTER>
<ENTER>
g()  ## prints 'Hi', because the function has now been defined

A super-cheesy way to get at the definition of g() would be:

eval(body(f)[[2]])
g()
## "Hi"

This picks the second element out of the expression that defines the function, which happens to be the definition of g(), and evaluates it in the current environment.

Upvotes: 1

Related Questions