1

In Python you can do this:

def boring_function(x):
            a = x + 1
            b = x - 1
            c = x / 1
    return a, b, c

a, b, c = boring_function(1)

I am looking for an equivalent method to use in R. So far, I have been trying to make an R list to retrieve the variables as follows:

boring_function = function(x) {
        
        a = x+1
        b = x-1
        c = x/1
        
        l = list(a = a, b = b, c = c)
        return(l)
}

result = boring_function(1)
a = result[["a"]]
b = result[["b"]]
c = result[["c"]]                   

However, note that this is greatly simplified example, and in reality I am making very complex functions that need to return values, lists etc separately. And in those cases, I have to return a single list that contain values, vectors, matrices, lists in lists etc. As you can imagine, the end result looks really messy and extremely hard to work with. Imagine, very often I have to do something like this in order to get my specific result from an R function:

specific_result = complex_function(x, y, z)[["some_list"]][["another_list"]][["yet_another_list"]][finally_the_result]

I find this method very confusing and I would prefer the Python approach. But I have to do my work in R. Seems like it is not possible to have the exact Python approach, but, is there anything similar in R? If not, what is the best way to do what I want to do?

bird
  • 2,938
  • 1
  • 6
  • 27
  • 1
    This is simply not supported in R. Hiw is that deeply nested list different in python? Surely you have to burrow deep than to get to that final result in python too? You can do this though: alist$somelist$anitherlist$somebame$finalresult – Sirius Mar 21 '21 at 09:47
  • 1
    Yeah, the problem with that `specific_result = ...` thing is a problem with how you've designed your data structures, not a problem with R. It wouldn't work any better in Python. – user2357112 Mar 21 '21 at 09:51
  • @Sirius, yes I still have to burrow deep in Python as well. However, it is somehow much less confusing to work with these stuff in Python compared to how I am doing it in R. And this is probably just a personal taste. Also, I felt like what I was doing in R would be wrong and not preferred by good programmers (I am relatively new). – bird Mar 21 '21 at 09:56
  • @user2357112supportsMonica I did not really claim that it is a problem in R. I asked if there are similar methods in R as I personally find the mentioned R method very confusing and complicated when I have to make complex functions. Also, if there is no such a way, I was still interested in another method that could be slightly easier than what I have been doing. :) – bird Mar 21 '21 at 09:59
  • @zed: This isn't something to solve with a nifty language feature. If you need to dig 4 levels down to get the information you want out of a function's return value, you have a design problem. – user2357112 Mar 21 '21 at 10:02
  • Also, the `list(a = a, b = b, c = c)` thing is very similar to Python namedtuples, which are often *preferred* over returning a bare tuple. Names are useful! They make code a lot clearer. `product_id = something.product_id` is a lot clearer than `product_id = something[5]`, and it's way harder to accidentally mix up names than it is to accidentally mix up indexes. – user2357112 Mar 21 '21 at 10:04
  • @zed `foo$bar$baz` is close enough to `foo.bar.baz` in that regard, can do away with the double brackets. – Sirius Mar 21 '21 at 10:05
  • Does this answer your question? [How to assign from a function which returns more than one value?](https://stackoverflow.com/questions/1826519/how-to-assign-from-a-function-which-returns-more-than-one-value) – qwr Mar 21 '21 at 10:41

2 Answers2

3

1) gsubfn The gsubfn package supports this. Try help("list", "gsubfn") for more examples.

library(gsubfn)

boring_function <- function(x) {
  a <- x + 1
  b <- x - 1
  c <- x / 1
  list(a, b, c)
}

list[a, b, c] <- boring_function(0)
a
## [1] 1
b
## [1] -1
c
## [1] 0

# target components can be left unspecified if we don't need them
list[aa,,cc] <- boring_function(0)
aa
## [1] 1
cc
## [1] 0

2) with Using with we can get a similar effect with the limitation that the variables will not survive the with; however, we can pass a single return value out of the with and the body of the with can be as long as you like with as many statements as needed. The function must return a named list in this case. (Also look at ?within for another variation.)

boring_function2 <- function(x) {
  a <- x + 1
  b <- x - 1
  c <- x / 1
  list(a = a, b = b, c = c)
}

with(boring_function2(0), {
   cat(a, b, c, "\n")
})
# at this point a, b and c are not defined

# we can work with a, b, and c in the with and in the end we only need Sum
Sum <- with(boring_function2(0), a + b + c)
Sum
## [1] 0

3) attach We can attach the list to the search path. This can be a bit confusing because variables in the workspace of the same name will mask those in the attached environment and, in general, this is not widely used or recommended but we show what is possible. As in (2) the function must return a named list.

attach(boring_function2(0), name = "boring")
a
## [1] 1

# found on search path
grep("boring", search(), value = TRUE)
## [1] "boring"

# remove from search path
detach("boring")

4) shortcuts If we have a named list but keep reusing the upper level we can refer to leaves in a variety of ways which may be more convenient in certain circumstances.

L <- list(a = list(a1 = 1, a2 = 2), b = list(b1 = 3, b2 = 4))

L[["a"]][["a1"]]
## [1] 1

La <- L$a
La$a1
## [1] 1

L[[c("a", "a1")]]
## [1] 1

ix <- c("a", "a1")
L[[ix]]
## [1] 1

L$a$a1
## [1] 1

L[[1:2]]
## [1] 2

5) walk tree If the elements of the nested list have unique names we can write a small function that will locate an element given its name.

findName <- function(L, name) {
  result <- NULL
  if (is.list(L)) {
    if (name %in% names(L)) result <- L[[name]]
    else for(el in L) {
       result <- Recall(el, name)
       if (!is.null(result)) break
    }
  }
  result
}

# test
L <- list(a = list(a1 = 1, a2 = 2, a3 = list(s = 11, t = 12), 
          b = list(b1 = 3, b2 = 4)))

# only need to specify a2 or a3 rather than entire path to it

a2 <- findName(L, "a2")
identical(a2, L$a$a2)
## [1] TRUE

a3 <- findName(L, "a3")
identical(a3, L$a$a3)
## [1] TRUE

We could also define our own class whose $ searches recursively thorugh the list for a given name. Here LL$a2 uses findName to locate the name a2.

"$.llist" <- findName
LL <- structure(L, class = "llist")
identical(LL$a2, LL$a$a2)
## [1] TRUE
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
1

As already stated in the comments this is not supported in R. It is a implementation choice in Python, and I would argue that storing your results as you are describing is bad design when programming in R.

In practice if you "pass" your list down, it makes better sense to append the result to your list

res <- list(res1 = a)
res$res2 <- b
# Continuing 

Or evaluate which parts of the result you need, and only return these parts of the result. Note that at this point you only need to subset the upper level res$final_result because it was appended at the upper level instead of appending to the level of res1 for example.

There is however a package that provides a similar interface to Pythons a, b, c = 1, 2, 3 called zeallot:

library(zeallot)
c(a, b, c) %<-% c(1, 2, 3)

(note the left hand needs to be a vector not a list), but this sacrifices some performance in your script. In general a better approach is to think about how "R" works with data, and store your results appropriately.

Oliver
  • 8,169
  • 3
  • 15
  • 37