[R] Evaluating lazily 'f<-' ?

Andrew Simmons @kw@|mmo @end|ng |rom gm@||@com
Fri Sep 17 04:09:12 CEST 2021


In your case, yes, there is a negative impact to formatting the code like:

x <- `padding<-`(right(x), ...)

and it comes from using 'substitute' without specifying an environment / /
list. It's my biggest problem with the tidyverse packages, the use of
non-standard evaluation. 'substitute' was originally created for simple
things like getting informative labels for data sets and plots, as well as
for 'delayedAssign'. For example, you could write your function to use the
above standard syntax, something like:

`padding<-` <- function (x, ..., value)
{
    sx <- substitute(x)
    choices <- c("bottom", "left", "top", "right")
    if (!is.call(sx) || !is.symbol(sx[[1L]]) ||
        !(k <- match(as.character(sx[[1L]]), choices, nomatch = 0L)))
        stop(gettextf("invalid 'x', must be a call to %s",
            paste(sQuote(choices), collapse = ", ")))
    choice <- choices[[k]]
    if (length(sx) != 2L)
        stop(gettextf("invalid 'x', %d arguments passed to '%s' which
requires 1",
            length(sx) - 1L, choice))
    x <- eval(sx[[2L]], parent.frame())
    # do whatever else with x, choice, ..., value
}

but given that you cannot use it like

padding(right(x)) <- 5

I can't say that I recommend it. Additionally, even if you define
`padding<-` like this, because of the non-standard evaluation from
'substitute', it means you CAN'T make a wrapper function for `padding<-`:

wrapper <- function (x, ..., value)
{
    # not sure exactly what this wrapper should do,
    # we'll add another element to 'value'
    `padding<-`(x = x, ..., value = c(list(value), "test"))
}

this won't work for a case like:

wrapper(right(letters))

because now, when you do 'substitute', it will give you the literal symbol
'x', not really what you wanted, because of the non-standard evaluation. Of
course, do whatever you want to make the syntax look the way you want, but
I think you're going about this the wrong way. I think the 'which' argument
I previously suggested will serve you far better. I hope this helps.

On Wed, Sep 15, 2021 at 2:26 AM Leonard Mada <leo.mada using syonic.eu> wrote:

> Hello Andrew,
>
>
> On 9/15/2021 6:53 AM, Andrew Simmons wrote:
>
> names(x) <- c("some names")
>
> if different from
>
> `names<-`(x, value = c("some names"))
>
> because the second piece of code does not ever call `<-`. The first piece
> of code is (approximately) equivalent to
>
> `*tmp*` <- x
> `*tmp*` <- `names<-`(`*tmp*`, value = c("some names"))
> x <- `*tmp*`
>
>
> This is my question:
>
> Would there be any negative impact if the code is changed to:
>
> x <- 'names<-'(x, value=...);
>
> The "x" will be evaluated inside 'names<-' and this function outputs &
> assigns "x". The "creation" of "x" in the current environment is done only
> after the call to 'names<-' (if it did not exist before).
>
>
> Leonard
>
>
>
> Another example,
>
> y <- `names<-`(x, value = c("some names"))
>
> now y will be equivalent to x if we did
>
> names(x) <- c("some names")
>
> except that the first will not update x, it will still have its old names.
>
> On Mon, Sep 13, 2021 at 4:33 PM Leonard Mada <leo.mada using syonic.eu> wrote:
>
>>
>> On 9/13/2021 11:28 PM, Andrew Simmons wrote:
>>
>> In the example you gave : r(x) <- 1
>> r(x) is never evaluated, the above calls `r<-`,
>> in fact r does not even have to be an existing function.
>>
>>
>> I meant:
>>
>> '*tmp*' <- x; # "x" is evaluated here;
>>
>> 'r<-' is called after this step, which makes sense in the case of
>> subsetting;
>>
>>
>> But I am wondering if changing this behaviour, when NO subsetting is
>> performed, would have any impact.
>>
>> e.g. names(x) = c("some names");
>>
>> # would it have any impact to skip the evaluation of "x" and call
>> directly:
>>
>> 'names<-'(x, value);
>>
>>
>> Leonard
>>
>>
>>
>> On Mon, Sep 13, 2021, 16:18 Leonard Mada <leo.mada using syonic.eu> wrote:
>>
>>> Hello,
>>>
>>>
>>> I have found the evaluation: it is described in the section on
>>> subsetting. The forced evaluation makes sense for subsetting.
>>>
>>>
>>> On 9/13/2021 9:42 PM, Leonard Mada wrote:
>>>
>>> Hello Andrew,
>>>
>>>
>>> I try now to understand the evaluation of the expression:
>>>
>>> e = expression(r(x) <- 1)
>>>
>>> # parameter named "value" seems to be required;
>>> 'r<-' = function(x, value) {print("R");}
>>> eval(e, list(x=2))
>>> # [1] "R"
>>>
>>> # both versions work
>>> 'r<-' = function(value, x) {print("R");}
>>> eval(e, list(x=2))
>>> # [1] "R"
>>>
>>>
>>> ### the Expression
>>> e[[1]][[1]] # "<-", not "r<-"
>>> e[[1]][[2]] # "r(x)"
>>>
>>>
>>> The evaluation of "e" somehow calls "r<-", but evaluates also the
>>> argument of r(...). I am still investigating what is actually happening.
>>>
>>>
>>> The forced evaluation is relevant for subsetting, e.g.:
>>> expression(r(x)[3] <- 1)
>>> expression(r(x)[3] <- 1)[[1]][[2]]
>>> # r(x)[3] # the evaluation details are NOT visible in the expression per
>>> se;
>>> # Note: indeed, it makes sens to first evaluate r(x) and then to perform
>>> the subsetting;
>>>
>>>
>>> However, in the case of a non-subsetted expression:
>>> r(x) <- 1;
>>> It would make sense to evaluate lazily r(x) if no subsetting is involved
>>> (more precisely "r<-"(x, value) ).
>>>
>>> Would this have any impact on the current code?
>>>
>>>
>>> Sincerely,
>>>
>>>
>>> Leonard
>>>
>>>
>>>
>>> Sincerely,
>>>
>>>
>>> Leonard
>>>
>>>
>>> On 9/13/2021 9:15 PM, Andrew Simmons wrote:
>>>
>>> R's parser doesn't work the way you're expecting it to. When doing an
>>> assignment like:
>>>
>>>
>>> padding(right(df)) <- 1
>>>
>>>
>>> it is broken into small stages. The guide "R Language Definition" claims
>>> that the above would be equivalent to:
>>>
>>>
>>> `<-`(df, `padding<-`(df, value = `right<-`(padding(df), value = 1)))
>>>
>>>
>>> but that is not correct, and you can tell by using `substitute` as you
>>> were above. There isn't a way to do what you want with the syntax you
>>> provided, you'll have to do something different. You could add a `which`
>>> argument to each style function, and maybe put the code for `match.arg` in
>>> a separate function:
>>>
>>>
>>> match.which <- function (which)
>>> match.arg(which, c("bottom", "left", "top", "right"), several.ok = TRUE)
>>>
>>>
>>> padding <- function (x, which)
>>> {
>>>     which <- match.which(which)
>>>     # more code
>>> }
>>>
>>>
>>> border <- function (x, which)
>>> {
>>>     which <- match.which(which)
>>>     # more code
>>> }
>>>
>>>
>>> some_other_style <- function (x, which)
>>> {
>>>     which <- match.which(which)
>>>     # more code
>>> }
>>>
>>>
>>> I hope this helps.
>>>
>>> On Mon, Sep 13, 2021 at 12:17 PM Leonard Mada <leo.mada using syonic.eu>
>>> wrote:
>>>
>>>> Hello Andrew,
>>>>
>>>>
>>>> this could work. I will think about it.
>>>>
>>>> But I was thinking more generically. Suppose we have a series of
>>>> functions:
>>>> padding(), border(), some_other_style();
>>>> Each of these functions has the parameter "right" (or the group of
>>>> parameters c("right", ...)).
>>>>
>>>>
>>>> Then I could design a function right(FUN) that assigns the value to
>>>> this parameter and evaluates the function FUN().
>>>>
>>>>
>>>> There are a few ways to do this:
>>>> 1.) Other parameters as ...
>>>> right(FUN, value, ...) = value; and then pass "..." to FUN.
>>>> right(value, FUN, ...) = value; # or is this the syntax? (TODO: explore)
>>>>
>>>> 2.) Another way:
>>>> right(FUN(...other parameters already specified...)) = value;
>>>> I wanted to explore this 2nd option: but avoid evaluating FUN, unless
>>>> the parameter "right" is injected into the call.
>>>>
>>>> 3.) Option 3:
>>>> The option you mentioned.
>>>>
>>>>
>>>> Independent of the method: there are still weird/unexplained behaviours
>>>> when I try the initial code (see the latest mail with the improved code).
>>>>
>>>>
>>>> Sincerely,
>>>>
>>>>
>>>> Leonard
>>>>
>>>>
>>>> On 9/13/2021 6:45 PM, Andrew Simmons wrote:
>>>>
>>>> I think you're trying to do something like:
>>>>
>>>> `padding<-` <- function (x, which, value)
>>>> {
>>>>     which <- match.arg(which, c("bottom", "left", "top", "right"),
>>>> several.ok = TRUE)
>>>>     # code to pad to each side here
>>>> }
>>>>
>>>> Then you could use it like
>>>>
>>>> df <- data.frame(x=1:5, y = sample(1:5, 5))
>>>> padding(df, "right") <- 1
>>>>
>>>> Does that work as expected for you?
>>>>
>>>> On Mon, Sep 13, 2021, 11:28 Leonard Mada via R-help <
>>>> r-help using r-project.org> wrote:
>>>>
>>>>> I try to clarify the code:
>>>>>
>>>>>
>>>>> ###
>>>>> right = function(x, val) {print("Right");};
>>>>> padding = function(x, right, left, top, bottom) {print("Padding");};
>>>>> 'padding<-' = function(x, ...) {print("Padding = ");};
>>>>> df = data.frame(x=1:5, y = sample(1:5, 5)); # anything
>>>>>
>>>>> ### Does NOT work as expected
>>>>> 'right<-' = function(x, value) {
>>>>>      print("This line should be the first printed!")
>>>>>      print("But ERROR: x was already evaluated, which printed
>>>>> \"Padding\"");
>>>>>      x = substitute(x); # x was already evaluated before substitute();
>>>>>      return("Nothing"); # do not now what the behaviour should be?
>>>>> }
>>>>>
>>>>> right(padding(df)) = 1;
>>>>>
>>>>> ### Output:
>>>>>
>>>>> [1] "Padding"
>>>>> [1] "This line should be the first printed!"
>>>>> [1] "But ERROR: x was already evaluated, which printed \"Padding\""
>>>>> [1] "Padding = " # How did this happen ???
>>>>>
>>>>>
>>>>> ### Problems:
>>>>>
>>>>> 1.) substitute(x): did not capture the expression;
>>>>> - the first parameter of 'right<-' was already evaluated, which is not
>>>>> the case with '%f%';
>>>>> Can I avoid evaluating this parameter?
>>>>> How can I avoid to evaluate it and capture the expression:
>>>>> "right(...)"?
>>>>>
>>>>>
>>>>> 2.) Unexpected
>>>>> 'padding<-' was also called!
>>>>> I did not know this. Is it feature or bug?
>>>>> R 4.0.4
>>>>>
>>>>>
>>>>> Sincerely,
>>>>>
>>>>>
>>>>> Leonard
>>>>>
>>>>>
>>>>> On 9/13/2021 4:45 PM, Duncan Murdoch wrote:
>>>>> > On 13/09/2021 9:38 a.m., Leonard Mada wrote:
>>>>> >> Hello,
>>>>> >>
>>>>> >>
>>>>> >> I can include code for "padding<-"as well, but the error is before
>>>>> that,
>>>>> >> namely in 'right<-':
>>>>> >>
>>>>> >> right = function(x, val) {print("Right");};
>>>>> >> # more options:
>>>>> >> padding = function(x, right, left, top, bottom) {print("Padding");};
>>>>> >> 'padding<-' = function(x, ...) {print("Padding = ");};
>>>>> >> df = data.frame(x=1:5, y = sample(1:5, 5));
>>>>> >>
>>>>> >>
>>>>> >> ### Does NOT work
>>>>> >> 'right<-' = function(x, val) {
>>>>> >>         print("Already evaluated and also does not use 'val'");
>>>>> >>         x = substitute(x); # x was evaluated before
>>>>> >> }
>>>>> >>
>>>>> >> right(padding(df)) = 1;
>>>>> >
>>>>> > It "works" (i.e. doesn't generate an error) for me, when I correct
>>>>> > your typo:  the second argument to `right<-` should be `value`, not
>>>>> > `val`.
>>>>> >
>>>>> > I'm still not clear whether it does what you want with that fix,
>>>>> > because I don't really understand what you want.
>>>>> >
>>>>> > Duncan Murdoch
>>>>> >
>>>>> >>
>>>>> >>
>>>>> >> I want to capture the assignment event inside "right<-" and then
>>>>> call
>>>>> >> the function padding() properly.
>>>>> >>
>>>>> >> I haven't thought yet if I should use:
>>>>> >>
>>>>> >> padding(x, right, left, ... other parameters);
>>>>> >>
>>>>> >> or
>>>>> >>
>>>>> >> padding(x, parameter) <- value;
>>>>> >>
>>>>> >>
>>>>> >> It also depends if I can properly capture the unevaluated expression
>>>>> >> inside "right<-":
>>>>> >>
>>>>> >> 'right<-' = function(x, val) {
>>>>> >>
>>>>> >> # x is automatically evaluated when using 'f<-'!
>>>>> >>
>>>>> >> # but not when implementing as '%f%' = function(x, y);
>>>>> >>
>>>>> >> }
>>>>> >>
>>>>> >>
>>>>> >> Many thanks,
>>>>> >>
>>>>> >>
>>>>> >> Leonard
>>>>> >>
>>>>> >>
>>>>> >> On 9/13/2021 4:11 PM, Duncan Murdoch wrote:
>>>>> >>> On 12/09/2021 10:33 a.m., Leonard Mada via R-help wrote:
>>>>> >>>> How can I avoid evaluation?
>>>>> >>>>
>>>>> >>>> right = function(x, val) {print("Right");};
>>>>> >>>> padding = function(x) {print("Padding");};
>>>>> >>>> df = data.frame(x=1:5, y = sample(1:5, 5));
>>>>> >>>>
>>>>> >>>> ### OK
>>>>> >>>> '%=%' = function(x, val) {
>>>>> >>>>        x = substitute(x);
>>>>> >>>> }
>>>>> >>>> right(padding(df)) %=% 1; # but ugly
>>>>> >>>>
>>>>> >>>> ### Does NOT work
>>>>> >>>> 'right<-' = function(x, val) {
>>>>> >>>>        print("Already evaluated and also does not use 'val'");
>>>>> >>>>        x = substitute(x); # is evaluated before
>>>>> >>>> }
>>>>> >>>>
>>>>> >>>> right(padding(df)) = 1
>>>>> >>>
>>>>> >>> That doesn't make sense.  You don't have a `padding<-` function,
>>>>> and
>>>>> >>> yet you are trying to call right<- to assign something to
>>>>> padding(df).
>>>>> >>>
>>>>> >>> I'm not sure about your real intention, but assignment functions by
>>>>> >>> their nature need to evaluate the thing they are assigning to,
>>>>> since
>>>>> >>> they are designed to modify objects, not create new ones.
>>>>> >>>
>>>>> >>> To create a new object, just use regular assignment.
>>>>> >>>
>>>>> >>> Duncan Murdoch
>>>>> >
>>>>>
>>>>> ______________________________________________
>>>>> R-help using r-project.org mailing list -- To UNSUBSCRIBE and more, see
>>>>> https://stat.ethz.ch/mailman/listinfo/r-help
>>>>> PLEASE do read the posting guide
>>>>> http://www.R-project.org/posting-guide.html
>>>>> and provide commented, minimal, self-contained, reproducible code.
>>>>>
>>>>

	[[alternative HTML version deleted]]



More information about the R-help mailing list