chaining closure arguments on-the-fly

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

chaining closure arguments on-the-fly

Benjamin Tyner
Greetings,

Occasionally, I desire to call a function with one argument set to equal
to another. Here is a toy example:

    f <- function(x, y) {

        x + y
    }

    f(x = 3, y = x) # Error in f(x = 3, y = x) : object 'x' not found

So far, the most concise way I found to accomplish this is:

    f(x = 3, y = local(sys.frame(1)$x)) # evaluates to 6

but I dislike this solution because local() creates a new environment.
Surely there must be a better way?

Note: I'm not interested in solutions that require modifying or currying f.

Regards,
Ben

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

R help mailing list-2
Hi Ben,

How about something like this:

f <- function(x, y = NULL) {

  if (is.null(y))
    y <- x

  x + y
}

> f(3, 4)
[1] 7

> f(3)
[1] 6

Regards,

Marc Schwartz


> On Jun 20, 2020, at 7:15 AM, Benjamin Tyner <[hidden email]> wrote:
>
> Greetings,
>
> Occasionally, I desire to call a function with one argument set to equal to another. Here is a toy example:
>
>    f <- function(x, y) {
>
>        x + y
>    }
>
>    f(x = 3, y = x) # Error in f(x = 3, y = x) : object 'x' not found
>
> So far, the most concise way I found to accomplish this is:
>
>    f(x = 3, y = local(sys.frame(1)$x)) # evaluates to 6
>
> but I dislike this solution because local() creates a new environment. Surely there must be a better way?
>
> Note: I'm not interested in solutions that require modifying or currying f.
>
> Regards,
> Ben
>
> ______________________________________________
> [hidden email] 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.

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Duncan Murdoch-2
In reply to this post by Benjamin Tyner
On 20/06/2020 7:15 a.m., Benjamin Tyner wrote:

> Greetings,
>
> Occasionally, I desire to call a function with one argument set to equal
> to another. Here is a toy example:
>
>      f <- function(x, y) {
>
>          x + y
>      }
>
>      f(x = 3, y = x) # Error in f(x = 3, y = x) : object 'x' not found
>
> So far, the most concise way I found to accomplish this is:
>
>      f(x = 3, y = local(sys.frame(1)$x)) # evaluates to 6
>
> but I dislike this solution because local() creates a new environment.
> Surely there must be a better way?
>
> Note: I'm not interested in solutions that require modifying or currying f.

How about

g <- function(x, y = x) {
   f(x, y)
}
g(x = 3)

or even

yEqualsX <- function(f) function(x, y = x) f(x, y)

yEqualsX(f)(x = 3)

These are a lot like currying, but aren't currying, so they may be
acceptable to you.  Personally I'd choose the first one.

Duncan Murdoch

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Benjamin Tyner
On 6/20/20 9:00 AM, Duncan Murdoch wrote:

> How about
>
> g <- function(x, y = x) {
>   f(x, y)
> }
> g(x = 3)
>
> or even
>
> yEqualsX <- function(f) function(x, y = x) f(x, y)
>
> yEqualsX(f)(x = 3)
>
> These are a lot like currying, but aren't currying, so they may be
> acceptable to you.  Personally I'd choose the first one.
>
> Duncan Murdoch
>
Thank you Duncan; I should have been more explicit that I was also
trying to avoid defining any new functions, but yes, it's hard to argue
with the wrapper approach as a longstanding best practice.

Basically I'm wondering if it would be theoretically possible to
construct a function "g" (possibly it would have to be
primitive/internal) such that

    f(x = 3, y = g(expr))

would evaluate expr in the evaluation environment of f? After rereading
section 4.3.3 of the R Language Definition, it's clear that supplied
arguments are evaluated in the calling environment; though trickery in
f()'s body may be used to evaluate elsewhere, such options seem limited
from within the argument list itself.

Ben

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Duncan Murdoch-2
On 20/06/2020 4:44 p.m., Benjamin Tyner wrote:

> On 6/20/20 9:00 AM, Duncan Murdoch wrote:
>> How about
>>
>> g <- function(x, y = x) {
>>    f(x, y)
>> }
>> g(x = 3)
>>
>> or even
>>
>> yEqualsX <- function(f) function(x, y = x) f(x, y)
>>
>> yEqualsX(f)(x = 3)
>>
>> These are a lot like currying, but aren't currying, so they may be
>> acceptable to you.  Personally I'd choose the first one.
>>
>> Duncan Murdoch
>>
> Thank you Duncan; I should have been more explicit that I was also
> trying to avoid defining any new functions, but yes, it's hard to argue
> with the wrapper approach as a longstanding best practice.
>
> Basically I'm wondering if it would be theoretically possible to
> construct a function "g" (possibly it would have to be
> primitive/internal) such that
>
>      f(x = 3, y = g(expr))
>
> would evaluate expr in the evaluation environment of f? After rereading
> section 4.3.3 of the R Language Definition, it's clear that supplied
> arguments are evaluated in the calling environment; though trickery in
> f()'s body may be used to evaluate elsewhere, such options seem limited
> from within the argument list itself.

I think you effectively did that in your original post (all but
encapsulating the expression in a function), so yes, it's possible.
However, it's a really bad idea.  Why use non-standard evaluation when
standard evaluation is fine?  Standard evaluation follows some well
defined rules, and is easy to reason about.  NSE follows whatever rules
it wants, so it's really hard for users to follow.  For example,
assuming you had the g() you want, what would this give?

z <- 3
f(x = z, y = g(z))

You can't possibly know that without knowing whether there's a local
variable in f named z that is created before y is evaluated.

Duncan Murdoch

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Bert Gunter-2
Gents:
(with trepidation)

f(x = 3, y = g(expr))
**already** evaluates g in the environment of f, **not** in the environment
of the caller.
(This does not contradict Duncan's example -- 3 is a constant, not a
variable).

e.g.
> f <- function(x = 3, y = x^2 +k){
+     k <- 3
+     x + y
+ }

Ergo
> k <- 100; x <- 10
> f()
[1] 15
> f(0)
[1] 3
> x
[1] 10

This is all due to lazy evaluation where default arguments are evaluated in
the function's environment (using standard evaluation). Arguments supplied
in the call are evaluated in the caller's environment, so:

> f(x = x)
[1] 113

Am I missing something here?

Cheers,

Bert Gunter

"The trouble with having an open mind is that people keep coming along and
sticking things into it."
-- Opus (aka Berkeley Breathed in his "Bloom County" comic strip )


On Sat, Jun 20, 2020 at 2:05 PM Duncan Murdoch <[hidden email]>
wrote:

> On 20/06/2020 4:44 p.m., Benjamin Tyner wrote:
> > On 6/20/20 9:00 AM, Duncan Murdoch wrote:
> >> How about
> >>
> >> g <- function(x, y = x) {
> >>    f(x, y)
> >> }
> >> g(x = 3)
> >>
> >> or even
> >>
> >> yEqualsX <- function(f) function(x, y = x) f(x, y)
> >>
> >> yEqualsX(f)(x = 3)
> >>
> >> These are a lot like currying, but aren't currying, so they may be
> >> acceptable to you.  Personally I'd choose the first one.
> >>
> >> Duncan Murdoch
> >>
> > Thank you Duncan; I should have been more explicit that I was also
> > trying to avoid defining any new functions, but yes, it's hard to argue
> > with the wrapper approach as a longstanding best practice.
> >
> > Basically I'm wondering if it would be theoretically possible to
> > construct a function "g" (possibly it would have to be
> > primitive/internal) such that
> >
> >      f(x = 3, y = g(expr))
> >
> > would evaluate expr in the evaluation environment of f? After rereading
> > section 4.3.3 of the R Language Definition, it's clear that supplied
> > arguments are evaluated in the calling environment; though trickery in
> > f()'s body may be used to evaluate elsewhere, such options seem limited
> > from within the argument list itself.
>
> I think you effectively did that in your original post (all but
> encapsulating the expression in a function), so yes, it's possible.
> However, it's a really bad idea.  Why use non-standard evaluation when
> standard evaluation is fine?  Standard evaluation follows some well
> defined rules, and is easy to reason about.  NSE follows whatever rules
> it wants, so it's really hard for users to follow.  For example,
> assuming you had the g() you want, what would this give?
>
> z <- 3
> f(x = z, y = g(z))
>
> You can't possibly know that without knowing whether there's a local
> variable in f named z that is created before y is evaluated.
>
> Duncan Murdoch
>
> ______________________________________________
> [hidden email] 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]]

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Benjamin Tyner

On 6/20/20 5:49 PM, Bert Gunter wrote:

> Gents:
> (with trepidation)
>
> f(x = 3, y = g(expr))
> **already** evaluates g in the environment of f, **not** in the
> environment of the caller.
> (This does not contradict Duncan's example -- 3 is a constant, not a
> variable).
>
> e.g.
> > f <- function(x = 3, y = x^2 +k){
> +     k <- 3
> +     x + y
> + }
>
> Ergo
> > k <- 100; x <- 10
> > f()
> [1] 15
> > f(0)
> [1] 3
> > x
> [1] 10
>
> This is all due to lazy evaluation where default arguments are
> evaluated in the function's environment (using standard evaluation).
> Arguments supplied in the call are evaluated in the caller's
> environment, so:
>
> > f(x = x)
> [1] 113
>
> Am I missing something here?
>
> Cheers,
>
> Bert Gunter
>
> "The trouble with having an open mind is that people keep coming along
> and sticking things into it."
> -- Opus (aka Berkeley Breathed in his "Bloom County" comic strip )
>
Default arguments are indeed evaluated in f's environment, but not
supplied arguments. I haven't really thought about the semantics of 'g'
with respect to default arguments. But certainly, lazy evaluation is key
here.

Ben (with trepidation as well)

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Bert Gunter-2
OK -- you were referring explicitly to the function call. That's what I
missed. Apologies for the noise.

-- Bert

On Sat, Jun 20, 2020 at 3:19 PM Benjamin Tyner <[hidden email]> wrote:

>
> On 6/20/20 5:49 PM, Bert Gunter wrote:
> > Gents:
> > (with trepidation)
> >
> > f(x = 3, y = g(expr))
> > **already** evaluates g in the environment of f, **not** in the
> > environment of the caller.
> > (This does not contradict Duncan's example -- 3 is a constant, not a
> > variable).
> >
> > e.g.
> > > f <- function(x = 3, y = x^2 +k){
> > +     k <- 3
> > +     x + y
> > + }
> >
> > Ergo
> > > k <- 100; x <- 10
> > > f()
> > [1] 15
> > > f(0)
> > [1] 3
> > > x
> > [1] 10
> >
> > This is all due to lazy evaluation where default arguments are
> > evaluated in the function's environment (using standard evaluation).
> > Arguments supplied in the call are evaluated in the caller's
> > environment, so:
> >
> > > f(x = x)
> > [1] 113
> >
> > Am I missing something here?
> >
> > Cheers,
> >
> > Bert Gunter
> >
> > "The trouble with having an open mind is that people keep coming along
> > and sticking things into it."
> > -- Opus (aka Berkeley Breathed in his "Bloom County" comic strip )
> >
> Default arguments are indeed evaluated in f's environment, but not
> supplied arguments. I haven't really thought about the semantics of 'g'
> with respect to default arguments. But certainly, lazy evaluation is key
> here.
>
> Ben (with trepidation as well)
>
>

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Benjamin Tyner
In reply to this post by Duncan Murdoch-2
On 6/20/20 5:04 PM, Duncan Murdoch wrote:

> I think you effectively did that in your original post (all but
> encapsulating the expression in a function), so yes, it's possible.
> However, it's a really bad idea.  Why use non-standard evaluation when
> standard evaluation is fine?  Standard evaluation follows some well
> defined rules, and is easy to reason about.  NSE follows whatever
> rules it wants, so it's really hard for users to follow.  For example,
> assuming you had the g() you want, what would this give?
>
> z <- 3
> f(x = z, y = g(z))
>
> You can't possibly know that without knowing whether there's a local
> variable in f named z that is created before y is evaluated.
>
> Duncan Murdoch
>
>
Very good point, and I agree it's best to use standard evaluation
whenever possible. The role of g would essentially be to modify one or
more elements of f's formals in-place. For example calling:

    f(x = 3, y = g(expr))

would be equivalent to calling a function fm:

    fm(x = 3)

where the body of fm is identical to that of f, but:

     > formals(fm)
    $x


    $y
    expr

though I expect g would be non-trivial to code up robustly.

______________________________________________
[hidden email] 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.
Reply | Threaded
Open this post in threaded view
|

Re: chaining closure arguments on-the-fly

Duncan Murdoch-2
On 21/06/2020 11:37 a.m., Benjamin Tyner wrote:

> On 6/20/20 5:04 PM, Duncan Murdoch wrote:
>> I think you effectively did that in your original post (all but
>> encapsulating the expression in a function), so yes, it's possible.
>> However, it's a really bad idea.  Why use non-standard evaluation when
>> standard evaluation is fine?  Standard evaluation follows some well
>> defined rules, and is easy to reason about.  NSE follows whatever
>> rules it wants, so it's really hard for users to follow.  For example,
>> assuming you had the g() you want, what would this give?
>>
>> z <- 3
>> f(x = z, y = g(z))
>>
>> You can't possibly know that without knowing whether there's a local
>> variable in f named z that is created before y is evaluated.
>>
>> Duncan Murdoch
>>
>>
> Very good point, and I agree it's best to use standard evaluation
> whenever possible. The role of g would essentially be to modify one or
> more elements of f's formals in-place. For example calling:
>
>      f(x = 3, y = g(expr))
>
> would be equivalent to calling a function fm:
>
>      fm(x = 3)
>
> where the body of fm is identical to that of f, but:
>
>       > formals(fm)
>      $x
>
>
>      $y
>      expr
>
> though I expect g would be non-trivial to code up robustly.

I would still say that's a bad idea.  The defaults for a function's
arguments are decided by the function's author, so it makes sense for
them to be evaluated in the evaluation frame of the function.  The
actual arguments given to the function are decided by whoever calls it,
so they should be evaluated in the caller's environment.  That's
standard evaluation.

What you're trying to do mixes up these things:  you want the caller to
be able to evaluate things that depend on function internals.  That is
bad, because it means the function can no longer be a black box that
does what it is documented to do:  it also needs to do it in exactly the
way it did when the caller wrote g(expr), or that could give a garbage
value.  Effectively you've broken the encapsulation that functions give
you.  You've made f() part of whatever function calls it (but imposed
the rule that says the source of f() can't be modified, which makes no
sense).

Duncan Murdoch

______________________________________________
[hidden email] 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.