|
I'm not sure if this is a known peculiarity or a bug, but I stumbled across what I think is very odd behavior from delayedAssign. In the below example x switches values the first two times it is evaluated.
> delayedAssign("x", {x <- 2; x+3}) > x==x [1] FALSE > delayedAssign("x", {x <- 2; x+3}) > x [1] 5 > x [1] 2 The ?delayedAssign documentation says that "after [evaluation], the value is fixed and the expression will not be evaluated again." However, this appears not to be true. Is this a bug, or just a good way to write extremely obfuscated code? Robert McGehee, CFA Geode Capital Management, LLC One Post Office Square, 28th Floor | Boston, MA | 02109 Direct: (617)392-8396 This e-mail, and any attachments hereto, are intended fo...{{dropped:14}} ______________________________________________ [hidden email] mailing list https://stat.ethz.ch/mailman/listinfo/r-devel |
|
For the amusement of the listserver:
Making use of the counter-intuitive assignment properties of delayedAssign, a co-worked challenged me to construct a delayedAssign of 'x' that causes 'x' to change its value _every_ time it is evaluated. The example below does this; each time 'x' is evaluated it is updated to be the next value in the Fibonnacci sequence. cmd <- parse(text= "delayedAssign(\"x\", { x <- y[1]+y[2] y[1] <- y[2] y[2] <- x eval(cmd) y[1] })") y <- c(0,1) eval(cmd) for (i in 1:20) print(x) [1] 1 [1] 1 [1] 2 [1] 3 [1] 5 [1] 8 [1] 13 [1] 21 [1] 34 [1] 55 [1] 89 [1] 144 [1] 233 [1] 377 [1] 610 [1] 987 [1] 1597 [1] 2584 [1] 4181 [1] 6765 Cheers, Robert -----Original Message----- From: [hidden email] [mailto:[hidden email]] On Behalf Of McGehee, Robert Sent: Wednesday, April 25, 2012 5:19 PM To: [hidden email] Subject: [Rd] delayedAssign changing values I'm not sure if this is a known peculiarity or a bug, but I stumbled across what I think is very odd behavior from delayedAssign. In the below example x switches values the first two times it is evaluated. > delayedAssign("x", {x <- 2; x+3}) > x==x [1] FALSE > delayedAssign("x", {x <- 2; x+3}) > x [1] 5 > x [1] 2 The ?delayedAssign documentation says that "after [evaluation], the value is fixed and the expression will not be evaluated again." However, this appears not to be true. Is this a bug, or just a good way to write extremely obfuscated code? Robert McGehee, CFA Geode Capital Management, LLC One Post Office Square, 28th Floor | Boston, MA | 02109 Direct: (617)392-8396 This e-mail, and any attachments hereto, are intended fo...{{dropped:14}} ______________________________________________ [hidden email] mailing list https://stat.ethz.ch/mailman/listinfo/r-devel ______________________________________________ [hidden email] mailing list https://stat.ethz.ch/mailman/listinfo/r-devel |
|
In reply to this post by McGehee, Robert
On Apr 25, 2012, at 5:18 PM, McGehee, Robert wrote: > I'm not sure if this is a known peculiarity or a bug, but I stumbled across what I think is very odd behavior from delayedAssign. In the below example x switches values the first two times it is evaluated. > >> delayedAssign("x", {x <- 2; x+3}) >> x==x > [1] FALSE > >> delayedAssign("x", {x <- 2; x+3}) >> x > [1] 5 >> x > [1] 2 > > The ?delayedAssign documentation says that "after [evaluation], the value is fixed and the expression will not be evaluated again." However, this appears not to be true. It is actually true, the value is not evaluated twice: your evaluation of "x" has modified the value of "x" (hence it is no longer a promise) so what you get in the next evaluation is the value that you have set as a side-effect of the promise evaluation. However, the forced value of the promise it still the value returned by the evaluated expression. YOu can see that it is the case by simply adding a function with side-effect (like cat) into your expression. > Is this a bug, or just a good way to write extremely obfuscated code? > That is a good question and I don't know the answer. The docs don't say which value will be fixed so it could be either way, but intuitively I would expect the promise evaluation to override side-effects. Cheers, Simon ______________________________________________ [hidden email] mailing list https://stat.ethz.ch/mailman/listinfo/r-devel |
|
It is really strange that the delayedAssign is evaluated in the environment it is called from, and thus can have side effects.
so x=2 y=3 delayedAssign("x", {y <- 7; y+3}) gives > x [1] 10 > y [1] 7 Both x and y changed. More intuitive would have been the behavior x=2 y=3 delayedAssign("x", local({y <- 7; y+3}) ) > x [1] 10 > y [1] 3 which only changes x. Or, at least that should be the default behavior.... Michael |
|
On Apr 26, 2012, at 11:59 AM, ghostwheel wrote: > It is really strange that the delayedAssign is evaluated in the environment it is called from, Not quite, it is evaluated in the environment you specify - and you have control over both environments ... see ?delayedAssign > and thus can have side effects. > so > x=2 > y=3 > delayedAssign("x", {y <- 7; y+3}) > > gives >> x > [1] 10 >> y > [1] 7 > > Both x and y changed. > More intuitive would have been the behavior > x=2 > y=3 > delayedAssign("x", local({y <- 7; y+3}) ) >> x > [1] 10 >> y > [1] 3 > which only changes x. > Or, at least that should be the default behavior.... > That is questionable - I think it is more logical for both environments to be the same as default. Just think if it -- the point here is to access lazy evaluation which is exactly what it does - lazy evaluation takes place in the original environment, not in another one. Cheers, Simon > Michael > > -- > View this message in context: http://r.789695.n4.nabble.com/delayedAssign-changing-values-tp4588108p4590242.html > Sent from the R devel mailing list archive at Nabble.com. > > ______________________________________________ > [hidden email] mailing list > https://stat.ethz.ch/mailman/listinfo/r-devel > > ______________________________________________ [hidden email] mailing list https://stat.ethz.ch/mailman/listinfo/r-devel |
I think I finally understand. My intuition just came from looking at ?delayedAssign. But delayedAssign came to replace delay(), which "creates a promise to evaluate the given expression". When one thinks of delay(), what you said makes sense, you just delay executing a certain expression in the parent frame. I think, though, that with the current way it is described and called, delayedAssign should by default only have the side effect of changing the variable, i.e. use eval.env=new.env(). The manual states: This function is invoked for its side effect, which is assigning a promise to evaluate value to the variable x. I think that is a nice clear side effect - changing a variable when it is evaluated...like a delayed "<<-". Otherwise it seems to me that delayedAssign could cause debugging nightmares. Luckily, it currently doesn't seem to widely used to cause them.... But you are right that it might be a bit strange that assign.env and eval.env are different. Maybe that is why there are two different parameters - to make the side effects clearer? I tried to find anywhere uses of delayedAssign which make positive use of side effects other than the assignment, and couldn't find any. Does anyone know of such a use? P.S. the end of ?delayedAssign contains this cryptic code: e <- (function(x, y = 1, z) environment())(1+2, "y", {cat(" HO! "); pi+2}) (le <- as.list(e)) # evaluates the promises Which I think is another way to create a promise, other than delayedAssign. But it is really unclear why it sits there at the bottom of the document. There should probably be more explanation of what this is.... |
|
On Apr 27, 2012, at 00:10 , ghostwheel wrote: > > Simon Urbanek wrote >> >>> More intuitive would have been the behavior >>> delayedAssign("x", local({y <- 7; y+3}) ) >>> which only changes x. >> >> That is questionable - I think it is more logical for both environments to >> be the same as default. Just think if it -- the point here is to access >> lazy evaluation which is exactly what it does - lazy evaluation takes >> place in the original environment, not in another one. >> > > I think I finally understand. My intuition just came from looking at > ?delayedAssign. > But delayedAssign came to replace delay(), which "creates a promise to > evaluate the given expression". > When one thinks of delay(), what you said makes sense, you just delay > executing a certain expression in the parent frame. > > I think, though, that with the current way it is described and called, > delayedAssign should by default only have the side effect of changing the > variable, i.e. use eval.env=new.env(). That's not possible. It involves evaluating an expression, and there is no limit to what side effect this can have. > > The manual states: > This function is invoked for its side effect, which is assigning a promise > to evaluate value to the variable x. > > I think that is a nice clear side effect - changing a variable when it is > evaluated...like a delayed "<<-". > Otherwise it seems to me that delayedAssign could cause debugging > nightmares. Luckily, it currently doesn't seem to widely used to cause > them.... Just don't do that, then.... However, lazy evaluation _per se_ does cause nightmares, or at least surprising behavior. My favorite one (because it actually involves a relevant piece of statistics) is loglike <- function(x,n) function(p) dbinom(x, n, p, log=TRUE) n <- 10 x <- 7 ll <- loglike(x, n) x <- 1 curve(ll) # max at 0.1 which has the issue that x (and n too) is not evaluated until the ll function is called, at which time it may have been changed from the value it had when ll was created. > > But you are right that it might be a bit strange that assign.env and > eval.env are different. Maybe that is why there are two different parameters > - to make the side effects clearer? > I tried to find anywhere uses of delayedAssign which make positive use of > side effects other than the assignment, and couldn't find any. Does anyone > know of such a use? > They'll have to be rather contrived, but printing is one, and perhaps maintaining a count of function calls could be another. > P.S. the end of ?delayedAssign contains this cryptic code: > > e <- (function(x, y = 1, z) environment())(1+2, "y", {cat(" HO! "); pi+2}) > (le <- as.list(e)) # evaluates the promises > > Which I think is another way to create a promise, other than delayedAssign. > But it is really unclear why it sits there at the bottom of the document. > There should probably be more explanation of what this is.... It's actually the _normal_ way to create a promise, namely binding actual arguments to formal arguments. It is just that some trickery is used in order to make the situation visible. I agree that the example looks a bit out of place, though. Perhaps there ought to be a help page on lazy evaluation and a reference to it? (Any volunteers?) -- Peter Dalgaard, Professor, Center for Statistics, Copenhagen Business School Solbjerg Plads 3, 2000 Frederiksberg, Denmark Phone: (+45)38153501 Email: [hidden email] Priv: [hidden email] ______________________________________________ [hidden email] mailing list https://stat.ethz.ch/mailman/listinfo/r-devel |
| Powered by Nabble | Edit this page |
