26 Environments

26.1 Environment basics

  1. Q: List three ways in which an environment differs from a list.
    A: The most important differences are:
    • environments have reference semantics
    • environments have parents
    • environments are not ordered
    • elements of environments need to be (uniquely) named
  2. Q: If you don’t supply an explicit environment, where do ls() and rm() look? Where does <- make bindings? The
    A: ls() and rm look in their calling environments which they find by as.environment(-1).
    From the book:

    Assignment is the act of binding (or rebinding) a name to a value in an environment.

    From ?`<-`:

    The operators <- and = assign into the environment in which they are evaluated. The operator <- can be used anywhere, whereas the operator = is only allowed at the top level (e.g., in the complete expression typed at the command prompt) or as one of the subexpressions in a braced list of expressions.

  3. Q: Using parent.env() and a loop (or a recursive function), verify that the ancestors of globalenv() include baseenv() and emptyenv(). Use the same basic idea to implement your own version of search().
    A: We can print the ancestors for example by using a recursive function:

    To implement a new version of search() we use a while statement:

26.2 Recursing over environments

  1. Q: Modify where() to find all environments that contain a binding for name.
    A: We look at the source code of the original pryr::where():

    Since where() stops searching when a match appears, we copy the recursive call in the else block to the block of the matching (“success”) case, so that our new function where2 will look for a binding within the complete search path. We also need to pay attention to other details. We have to take care to save the bindings in an object, while not overriding it in our recursive calls. So we create a list object for that and define a new function within where2() that we call where2.internal. where2.internal() will do the recursive work and whenever it finds a binding it will write it via <<- to the especially created list in its enclosing environment:

    Note that where2.internal() still provides the same structure as pryr::where does and you can also divide it in “base case”, “success case” and “recursive case”.

  2. Q: Write your own version of get() using a function written in the style of where().
    A: Note that get() provides a bit more arguments than our following version, but it should be easy to build up on that. However, we can change pryr::where to get2() with just changing one line of code (and the function name for the recursive call):

  3. Q: Write a function called fget() that finds only function objects. It should have two arguments, name and env, and should obey the regular scoping rules for functions: if there’s an object with a matching name that’s not a function, look in the parent. For an added challenge, also add an inherits argument which controls whether the function recurses up the parents or only looks in one environment.
    A: We can build up our function on the implementation of get2() in the last exercise. We only need to add a check via is.function(), change the name (also in the recursive call) and the error message:

    Note that this function is almost the same as the implementation of pryr::fget():

    We add an inherits parameter as described in the exercise:

  4. Q: Write your own version of exists(inherits = FALSE) (Hint: use ls().) Write a recursive version that behaves like exists(inherits = TRUE).
    A: We write two versions. exists2() will be the case inherits = FALSE and exists3() inherits = TRUE:

26.3 Function environments

  1. Q: List the four environments associated with a function. What does each one do? Why is the distinction between enclosing and binding environments particularly important?
    A:
    • Enclosing: where the function is created
    • Binding: where the function was assigned
    • Execution: a temporary environment which is created when the function is executed
    • Calling: the environment from where the function was called

    The difference between binding and enclosing environment is important, because of R’s lexical scoping rules. If R can’t find an object in the current environment while executing a function, it will look for it in the enclosing environment.

  2. Q: Draw a diagram that shows the enclosing environments of this function:

    A:

  3. Q: Expand your previous diagram to show function bindings.
    A:

  4. Q: Expand it again to show the execution and calling environments.
    A:

  5. Q: Write an enhanced version of str() that provides more information about functions. Show where the function was found and what environment it was defined in.
    A: Additionally we provide the function type in the sense of pryr::ftype. We use functions from the pryr package, since it provides helpers for all requested features:

    Note that we wanted to have non standard evaluation like the original str() function. Since pryr::where() doesn’t support non standard evaluation, we needed to catch the name of the supplied object. Therefore we used expr_text() from the lazyeval package. As a result, fstr(object = packagename::functionname) will result in an error in contrast to str().

26.4 Binding names to values

  1. Q: What does this function do? How does it differ from <<- and why might you prefer it?

    A: The function does “more or less” the same as <<-. Additionally to <<- it has an env argument, but this is not a big advantage, since also assign() provides this functionality. The main difference is that rebind() only does an assignment, when it finds a binding in one of the parent environments of env. Whereas:

    If <<- doesn’t find an existing variable, it will create one in the global environment. This is usually undesirable, because global variables introduce non-obvious dependencies between functions.

  2. Q: Create a version of assign() that will only bind new names, never re-bind old names. Some programming languages only do this, and are known as [single assignment languages][single assignment].
    A: We take the formals from assign()’s source code and define our new function. If x already exists, we give a message and return NULL (since this is the same as return()). Otherwise we let the body of the assign() function do the work:

    Note that .Internal(assign(x, value, envir, inherits)), is not inside an else block or any other function. This is important. Otherwise we would change more of assign() than we want (in case of the assignment of a new function, the enclosing environment for that function would differ).

  3. Q: Write an assignment function that can do active, delayed, and locked bindings. What might you call it? What arguments should it take? Can you guess which sort of assignment it should do based on the input?
    A: The following might be no optimal solution, but we can at least handle two of three cases via if statements. The problem already occured in the last exercise, were we had to do an assignment in an if statement and did a workaround. This workaround only works for one assignment (for logical reasons). We still use the workaround for the “delay case”, but we found a solution for the other two cases. The main aspect in it is to unify the environment were assign(), makeActiveBinding() and delayedAssign() act. We also had to test that cases like this

    work with our new function and our function creates bindings (and so enclosing environments) in the same places as assign() would do, also when used inside funceions.

    The usage of pryr:::to_env() simplified this process a lot:

    We used all these thoughts to create the following function:

    At the moment we have no idea for a good default guess routine, so that a specific atype of assignment would be done based on the input.