############################################################## # # Implicit as opposed to declared local variable # (together with scoping) lead to some unnatural # (i.e. non-lexically scoped) behaviour. # # Solution is mostly to explicitly pass the variables # along in the function call. i.e. maintain control of them. # # Here is the simplest version. # x <- 1:4 y <- x * 10 foo <- function(x) { y <- x + y # ^local ^local ^global # left of the assignment, y is local, right it is global y <- x + y # ^local ^local ^local # from now on, y is local everywhere. list(x=x,y=y) } foo(x) # # Note the value of y # # Note also that the value of y outside of foo is not # changed. This is important. y # # This can have subtle negative effects, particularly with locally # defined functions where lexical scoping seems natural. # Eg. putting the above together, ... foo <- function(x) { z <- x * 10 y <- z * 10 w <- y * 10 bar <- function(x) { z <- x + z y <- x + y list(barx =x, bary = y, barz = z) } # One might call bar to add x to z and y barw <- bar(w) # but outside of bar these would not have changed. list(wfoo = w, xfoo= x, yfoo= y, zfoo= z, barw = barw) } foo(x) # Had z and y been treated globally on both sides of the assignment # within bar they would would have been updated within foo. # # The solution might be that the local function, bar, needs to pass # y and z as well as the value for x. # Here is a first attempt. foo <- function(x) { z <- x * 10 y <- z * 10 w <- y * 10 bar <- function(x,y,z) { z <- x + z y <- x + y list(barx =x, bary = y, barz = z) } # One might call bar to add x to z and y barw <- bar(w,y,z) # but outside of bar these would not have changed. list(wfoo = w, xfoo= x, yfoo= y, zfoo= z, barw = barw) } foo(x) # # which unfortunately does not have the desired effect. # These changes cannot be had by side effect, they must # be done by assigning the results of the function evaluation # as in # foo <- function(x) { z <- x * 10 y <- z * 10 w <- y * 10 bar <- function(x,y,z) { z <- x + z y <- x + y list(barx =x, bary = y, barz = z) } # One might call bar to add x to z and y barw <- bar(w,y,z) # Now do the assignment. y <- barw$bary z <- barw$barz # but outside of bar these would not have changed. list(wfoo = w, xfoo= x, yfoo= y, zfoo= z, barw = barw) } foo(x) # which brings us to the following (unattractive) solution # as simpler but which mixes local and parent values for # y and z within bar. foo <- function(x) { z <- x * 10 y <- z * 10 w <- y * 10 bar <- function(x) { z <- x + z y <- x + y list(barx =x, bary = y, barz = z) } # One might call bar to add x to z and y barw <- bar(w) # Now do the assignment. y <- barw$bary z <- barw$barz # but outside of bar these would not have changed. list(wfoo = w, xfoo= x, yfoo= y, zfoo= z, barw = barw) } foo(x) ######################################################################## # # Perhaps a more natural example where these concerns would arise # would be something like x foo <- function(x) { swap <- function(i,j) { tmp <- x[i] x[i] <- x[j] x[j] <- tmp } # One might call swap, hoping to interchange a pair of values # within the vector x. (usually this would be buried in a loop) swap(1,2) # but it won't work. list(xfoo= x) } foo(x) # Note that the swap function does not explicitly pass x # and referred only to two elements of x. # Nevertheless, the local x has (seemingly) inherited the entire # structure of the parent x, its length for example x foo <- function(x) { swap <- function(i,j) { tmp <- x[i] x[i] <- x[j] x[j] <- tmp print(length(x)) } # One might call swap, hoping to interchange a pair of values # within the vector x. (usually this would be buried in a loop) swap(1,2) # but it won't work. list(xfoo= x) } foo(x) # # which means that all of x was copied within swap, even # though only two elements were accessed. # # You might think that the solution is to simply make x an argument # to swap # x foo <- function(x) { swap <- function(x,i,j) { tmp <- x[i] x[i] <- x[j] x[j] <- tmp } # One might call swap, hoping to interchange a pair of values # within the vector x. (usually this would be buried in a loop) swap(x,1,2) # but it still won't work. list(xfoo= x) } foo(x) # # which still does not work. This might have been guessed since # the version without passing x still had access to the entire # structure of x (length, elements, etc.) # The solution is had by returning the local x as the value of the function # (as before). N.B. There is no need to have x appear as an argument # to the internal function here. x foo <- function(x) { swap <- function(i,j) { tmp <- x[i] x[i] <- x[j] x[j] <- tmp x } # One might call swap, hoping to interchange a pair of values # within the vector x. (usually this would be buried in a loop) # NOW assign the result back to x. x <- swap(1,2) # Unfortunately, this produces a lot of copying which is # not so good if x is large. list(xfoo= x) } foo(x) # This works but seems to be very bad style. # ################################################################################ # # Many programming languages are lexically scoped and so # transcribing a program from one such language to the S # language needs to be done with care. # ################################################################################ # # Here is an example of the quicksort algorithm (doubly recursive) # taken from http://math.uww.edu/~harrisb/courses/cs171/sorts.html # Language is TURBO PASCAL (here as a string, so that it can be harmlessly # evaluated). " PROCEDURE quick (var arr:list; max:integer); PROCEDURE sort(l,r: integer); VAR i, j : integer; x : string; BEGIN i := l; j := r; x := arr[(l+r) DIV 2]; REPEAT WHILE arr[i]j; IF l quicksort # sort --> mysort ... note that x is passed and returned in the S version. # A simple transliteration would fail. # quicksort <- function (x, max=length(x)) { ## implemented (doubly) recursively mysort <- function(x,l,r) { # scoping rules of R/S require that # x be passed as an argument, # otherwise value of x in # the environment at the time of # definition of mysortup is used ... rwo i <- l j <- r h <- x[floor((l+r)/2)] ## div 2 repeat { while(x[i] < h) {i <- i+1} while(h < x[j]) {j <- j-1} if(i<=j) { ## swap x[i] and x[j] tmp <- x[i] x[i] <- x[j] x[j] <- tmp i <- i+1 j <- j-1 } ## end if if(i > j) {break()} else {next()} } ## end repeat if(l < j) {mysort(x,l,j)} if(i < r) {mysort(x,i,r)} x} ## end of mysort # need to return x inside mysortup # or it will be lost since x outside # was not altered. ... rwo mysort(x,1,max) } quicksort(5:1)