Dear R community, I do know, that an R function is constructing a copy of any object passed as argument into a function. I program on a larger S4 project for a package, and I arrived at a point where I have to think a little harder on implementation style (especially to spare users complex object handling). I have a function foo(), taking as input arguments two S4 objects of different class type foo <- function(o1, o2) { o1 at att1 <- producesomething() o2 at att2 <- producesomethingelse() } Of course, this functions does not change the objects in the global environment. Now I have two choices 1. Change the objects and return a list with both objects: foo <- function(o1, o2) { o1 at att1 <- producesomething() o2 at att2 <- producesomethingelse() l <- list(O1 = o1, O2 = o2) return(l) } This is cumbersome for users, as they have then to assign the objects inside the returned list to the symbols used in the global environment. But it is the way intended by R. 2. Change the objects of the global environment inside the function: foo <- function(o1, o2) { o1 at att1 <- producesomething() o2 at att2 <- producesomethingelse() assign("o1", o1, .GlobalEnv) assign("o2", o2, .GlobalEnv) } This is for users very practical, as the symbols used before refer now to objects with changed attributes and can be used immediately. BUT: It is not intended to be done like this. I spared any solution using one function for every single object type, as this becomes even more cumbersome than choice 1 above, in my opinion. What is your opinion on the trade-off between user-friendly handling of objects and R-intended programming style? Do I miss another choice, combining both? Best Simon
On Thu, Jan 31, 2013 at 11:52 AM, Simon Zehnder <szehnder at uni-bonn.de> wrote:> Dear R community, > > I do know, that an R function is constructing a copy of any object passed as argument into a function. I program on a larger S4 project for a package, and I arrived at a point where I have to think a little harder on implementation style (especially to spare users complex object handling). > > I have a function foo(), taking as input arguments two S4 objects of different class type > > foo <- function(o1, o2) { > o1 at att1 <- producesomething() > o2 at att2 <- producesomethingelse() > > } > > Of course, this functions does not change the objects in the global environment. Now I have two choices > > 1. Change the objects and return a list with both objects: > > foo <- function(o1, o2) { > o1 at att1 <- producesomething() > o2 at att2 <- producesomethingelse() > > l <- list(O1 = o1, O2 = o2) > return(l) > } > > This is cumbersome for users, as they have then to assign the objects inside the returned list to the symbols used in the global environment. But it is the way intended by R.By cumbersome you mean the following (anti-?) pattern has to be used, for example in a loop: o1 = something() o2 = somethingelse() o12 = foo(o1,o2) o1 = o12$o1 o2 = o12$o2 so that at the end of it you have a modified o1 and o2? I suggest that if you have a function that modifies more than one of its arguments, then there is a strong case for making those arguments a single argument. So that you'd do: foo = function(o12){ o12$o1 at att=bar() o12$o2 at att=baz() return(o12) } o12 = list(something(), somethingelse()) o12 = foo(o12) and there you are, a modified o12 object. no packing/unpacking needed. I suspect that either your o1 and o2 are so closely related that you should pack them in a list (or ideally, an object) or differently related such that you should treat them separately and not tweak both of them within the same function. That approach is binding behaviour very tightly to two structures, and probably won't give you very debuggable modular code. B
On Thu, Jan 31, 2013 at 3:00 PM, Simon Zehnder <szehnder at uni-bonn.de> wrote:> Hi Barry, > > this actually a good idea, to put them together! Probably even creating an object containing both of them. Haven't thought about it before. >Hadley W asked for implementations of 'unstructuring assignments' the other day, and I bashed this out to a gist: https://gist.github.com/4543212 it lets you do: (a~b~c) = foo() and if foo() returns a list with three components it assigns them to a, b, and c. Equivalent to: tmp = foo() a=tmp[[1]] b=tmp[[2]] c=tmp[[3]] whether its any use to your use case or even works properly under all circumstances or even just serves to obfuscate things is a point for discussion... Barry
> it lets you do: > > (a~b~c) = foo()Mistook. should be: (a~b~c) %=% foo() because it defines the %=% operator. Barry
On Thu, Jan 31, 2013 at 6:52 AM, Simon Zehnder <szehnder at uni-bonn.de> wrote:> Dear R community, > > I do know, that an R function is constructing a copy of any object passed as argument into a function. I program on a larger S4 project for a package, and I arrived at a point where I have to think a little harder on implementation style (especially to spare users complex object handling). > > I have a function foo(), taking as input arguments two S4 objects of different class type > > foo <- function(o1, o2) { > o1 at att1 <- producesomething() > o2 at att2 <- producesomethingelse() > > } > > Of course, this functions does not change the objects in the global environment. Now I have two choices > > 1. Change the objects and return a list with both objects: > > foo <- function(o1, o2) { > o1 at att1 <- producesomething() > o2 at att2 <- producesomethingelse() > > l <- list(O1 = o1, O2 = o2) > return(l) > } > > This is cumbersome for users, as they have then to assign the objects inside the returned list to the symbols used in the global environment. But it is the way intended by R. > > 2. Change the objects of the global environment inside the function: > > foo <- function(o1, o2) { > o1 at att1 <- producesomething() > o2 at att2 <- producesomethingelse() > > assign("o1", o1, .GlobalEnv) > assign("o2", o2, .GlobalEnv) > } > > This is for users very practical, as the symbols used before refer now to objects with changed attributes and can be used immediately. BUT: It is not intended to be done like this. > > I spared any solution using one function for every single object type, as this becomes even more cumbersome than choice 1 above, in my opinion. > > What is your opinion on the trade-off between user-friendly handling of objects and R-intended programming style? Do I miss another choice, combining both? > >Another possibility is to create an environment component in your S4 class since those can be passed around as references: OO <- setClass("OO", representation(env = "environment")) setMethod("initialize", "OO", function(.Object) { .Object at env <- new.env() .Object }) setMethod("show", "OO", function(object) show(object at env$a)) o1 <- OO() o1 at env$a <- 1 o2 <- OO() o2 at env$a <- 2 o1 # 1 o2 # 2 swap <- function(o1, o2) { tmp <- o1 at env$a o1 at env$a <- o2 at env$a o2 at env$a <- tmp } swap(o1, o2) o1 # 2 o2 # 1 or if its feasible for you to change class systems Reference Classes already use environments: R <- setRefClass("R", fields = "a", methods = list( swap = function(o2) { tmp <- o2$a o2$a <- a a <<- tmp }, show = function() methods::show(a) ) ) r1 <- R$new(a = 1) r2 <- R$new(a = 2) r1 # 1 r2 # 2 r1$swap(r2) r1 # 2 r2 # 1 -- Statistics & Software Consulting GKX Group, GKX Associates Inc. tel: 1-877-GKX-GROUP email: ggrothendieck at gmail.com
Dear Barry, thank you very much for this information. This looks pretty interesting! Best Simon On Jan 31, 2013, at 4:09 PM, Barry Rowlingson <b.rowlingson at lancaster.ac.uk> wrote:>> it lets you do: >> >> (a~b~c) = foo() > > Mistook. should be: > > (a~b~c) %=% foo() > > because it defines the %=% operator. > > Barry