Why Is Using Assign Bad

Why is using assign bad?

Actually those two operations are quite different. The first gives you 26 different objects while the second gives you only one. The second object will be a lot easier to use in analyses. So I guess I would say you have already demonstrated the major downside of assign, namely the necessity of then needing always to use get for corralling or gathering up all the similarly named individual objects that are now "loose" in the global environment. Try imagining how you would serially do anything with those 26 separate objects. A simple lapply(foo, func) will suffice for the second strategy.

That FAQ citation really only says that using assignment and then assigning names is easier, but did not imply it was "bad". I happen to read it as "less functional" since you are not actually returning a value that gets assigned. The effect looks to be a side-effect (and in this case the assign strategy results in 26 separate side-effects). The use of assign seems to be adopted by people that are coming from languages that have global variables as a way of avoiding picking up the "True R Way", i.e. functional programming with data-objects. They really should be learning to use lists rather than littering their workspace with individually-named items.

There is another assignment paradigm that can be used:

 foo <- setNames(  paste0(letters,1:26),  LETTERS)

That creates a named atomic vector rather than a named list, but the access to values in the vector is still done with names given to [.

When is R's assign() function appropriate?

If you were constructing a program that mediated a dialogue with a user wherein the user was asked to input an arbitrary object name (in the specific R sense of an unquoted string that that is listed in a particular namespace), you might consider using assign.

The option to assign to a particular environment may also have value. Notice how it is used in the ecdf function:

ecdf
#----screen output----
function (x)
{
x <- sort(x)
n <- length(x)
if (n < 1)
stop("'x' must have 1 or more non-missing values")
vals <- unique(x)
rval <- approxfun(vals, cumsum(tabulate(match(x, vals)))/n,
method = "constant", yleft = 0, yright = 1, f = 0, ties = "ordered")
class(rval) <- c("ecdf", "stepfun", class(rval))
assign("nobs", n, envir = environment(rval))
attr(rval, "call") <- sys.call()
rval
}
<bytecode: 0x7c77cc0>
<environment: namespace:stats>

The ecdf function takes data and returns another function. Most of that function is built with a C call by approxfun, but as a last feature, the ecdf function adds an element to the environment of the returned value (which is yet another function.)

I'm sure you could find other instances where assign is used in the R code of the base and stats packages. Those are arguably "R Core Certified^({TM)}" examples of "proper" uses.

When I followed my own advice I got this from a bash operation:

$ cd '/home/david/Downloads/R-3.5.2/src/library/base/R/' 
$ grep -R "assign"
# --- results with a recent download of the R sources -----
userhooks.R: assign(hookName, new, envir = .userHooksEnv, inherits = FALSE)
datetime.R: cacheIt <- function(tz) assign(".sys.timezone", tz, baseenv())
autoload.R: assign(".Autoloaded", c(package, .Autoloaded), envir =.AutoloadEnv)
lazyload.R: ## set <- function (x, value, env) .Internal(assign(x, value, env, FALSE))
delay.R: function(x, value, eval.env=parent.frame(1), assign.env=parent.frame(1))
delay.R: .Internal(delayedAssign(x, substitute(value), eval.env, assign.env))
assign.R:# File src/library/base/R/assign.R
assign.R:assign <-
assign.R: .Internal(assign(x, value, envir, inherits))
# stripped out some occurences of "assighnment"
# stripped out the occurrences of "assign" in the namespace functions
zzz.R:assign("%*%", function(x, y) NULL, envir = .ArgsEnv)
zzz.R:assign("...length", function() NULL, envir = .ArgsEnv)
zzz.R:assign("...elt", function(n) NULL, envir = .ArgsEnv)
zzz.R:assign(".C", function(.NAME, ..., NAOK = FALSE, DUP = TRUE, PACKAGE,
zzz.R:assign(".Fortran",
zzz.R:assign(".Call", function(.NAME, ..., PACKAGE) NULL, envir = .ArgsEnv)
zzz.R:assign(".Call.graphics", function(.NAME, ..., PACKAGE) NULL, envir = .ArgsEnv)
zzz.R:assign(".External", function(.NAME, ..., PACKAGE) NULL, envir = .ArgsEnv)
zzz.R:assign(".External2", function(.NAME, ..., PACKAGE) NULL, envir = .ArgsEnv)
zzz.R:assign(".External.graphics", function(.NAME, ..., PACKAGE) NULL,
zzz.R:assign(".Internal", function(call) NULL, envir = .ArgsEnv)
zzz.R:assign(".Primitive", function(name) NULL, envir = .ArgsEnv)
zzz.R:assign(".isMethodsDispatchOn", function(onOff = NULL) NULL, envir = .ArgsEnv)
zzz.R:assign(".primTrace", function(obj) NULL, envir = .ArgsEnv)
zzz.R:assign(".primUntrace", function(obj) NULL, envir = .ArgsEnv)
zzz.R:assign(".subset", function(x, ...) NULL, envir = .ArgsEnv)
zzz.R:assign(".subset2", function(x, ...) NULL, envir = .ArgsEnv)
zzz.R:assign("UseMethod", function(generic, object) NULL, envir = .ArgsEnv)
zzz.R:assign("as.call", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("attr", function(x, which, exact = FALSE) NULL, envir = .ArgsEnv)
zzz.R:assign("attr<-", function(x, which, value) NULL, envir = .ArgsEnv)
zzz.R:assign("attributes", function(obj) NULL, envir = .ArgsEnv)
zzz.R:assign("attributes<-", function(obj, value) NULL, envir = .ArgsEnv)
zzz.R:assign("baseenv", function() NULL, envir = .ArgsEnv)
zzz.R:assign("browser",
zzz.R:assign("call", function(name, ...) NULL, envir = .ArgsEnv)
zzz.R:assign("class", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("class<-", function(x, value) NULL, envir = .ArgsEnv)
zzz.R:assign(".cache_class", function(class, extends) NULL, envir = .ArgsEnv)
zzz.R:assign("emptyenv", function() NULL, envir = .ArgsEnv)
zzz.R:assign("enc2native", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("enc2utf8", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("environment<-", function(fun, value) NULL, envir = .ArgsEnv)
zzz.R:assign("expression", function(...) NULL, envir = .ArgsEnv)
zzz.R:assign("forceAndCall", function(n, FUN, ...) NULL, envir = .ArgsEnv)
zzz.R:assign("gc.time", function(on = TRUE) NULL, envir = .ArgsEnv)
zzz.R:assign("globalenv", function() NULL, envir = .ArgsEnv)
zzz.R:assign("interactive", function() NULL, envir = .ArgsEnv)
zzz.R:assign("invisible", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.atomic", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.call", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.character", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.complex", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.double", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.environment", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.expression", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.function", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.integer", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.language", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.list", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.logical", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.name", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.null", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.object", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.pairlist", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.raw", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.recursive", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.single", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("is.symbol", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("isS4", function(object) NULL, envir = .ArgsEnv)
zzz.R:assign("list", function(...) NULL, envir = .ArgsEnv)
zzz.R:assign("lazyLoadDBfetch", function(key, file, compressed, hook) NULL,
zzz.R:assign("missing", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("nargs", function() NULL, envir = .ArgsEnv)
zzz.R:assign("nzchar", function(x, keepNA=FALSE) NULL, envir = .ArgsEnv)
zzz.R:assign("oldClass", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("oldClass<-", function(x, value) NULL, envir = .ArgsEnv)
zzz.R:assign("on.exit", function(expr = NULL, add = FALSE, after = TRUE) NULL, envir = .ArgsEnv)
zzz.R:assign("pos.to.env", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("proc.time", function() NULL, envir = .ArgsEnv)
zzz.R:assign("quote", function(expr) NULL, envir = .ArgsEnv)
zzz.R:assign("retracemem", function(x, previous = NULL) NULL, envir = .ArgsEnv)
zzz.R:assign("seq_along", function(along.with) NULL, envir = .ArgsEnv)
zzz.R:assign("seq_len", function(length.out) NULL, envir = .ArgsEnv)
zzz.R:assign("standardGeneric", function(f, fdef) NULL, envir = .ArgsEnv)
zzz.R:assign("storage.mode<-", function(x, value) NULL, envir = .ArgsEnv)
zzz.R:assign("substitute", function(expr, env) NULL, envir = .ArgsEnv)
zzz.R:assign("switch", function(EXPR, ...) NULL, envir = .ArgsEnv)
zzz.R:assign("tracemem", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("unclass", function(x) NULL, envir = .ArgsEnv)
zzz.R:assign("untracemem", function(x) NULL, envir = .ArgsEnv)
zzz.R: assign(f, fx, envir = env) # grep fails to include the names of these
zzz.R: assign(f, fx, envir = env)
zzz.R: assign(f, fx, envir = env)
zzz.R: assign(f, fx, envir = env)
zzz.R: assign(f, fx, envir = env)
zzz.R: assign("anyNA", fx, envir = env)
zzz.R:assign("!", function(x) UseMethod("!"), envir = .GenericArgsEnv)
zzz.R:assign("as.character", function(x, ...) UseMethod("as.character"),
zzz.R:assign("as.complex", function(x, ...) UseMethod("as.complex"),
zzz.R:assign("as.double", function(x, ...) UseMethod("as.double"),
zzz.R:assign("as.integer", function(x, ...) UseMethod("as.integer"),
zzz.R:assign("as.logical", function(x, ...) UseMethod("as.logical"),
zzz.R:#assign("as.raw", function(x) UseMethod("as.raw"), envir = .GenericArgsEnv)
zzz.R:## assign("c", function(..., recursive = FALSE, use.names = TRUE) UseMethod("c"),
zzz.R:assign("c", function(...) UseMethod("c"),
zzz.R:#assign("dimnames", function(x) UseMethod("dimnames"), envir = .GenericArgsEnv)
zzz.R:assign("dim<-", function(x, value) UseMethod("dim<-"), envir = .GenericArgsEnv)
zzz.R:assign("dimnames<-", function(x, value) UseMethod("dimnames<-"),
zzz.R:assign("length<-", function(x, value) UseMethod("length<-"),
zzz.R:assign("levels<-", function(x, value) UseMethod("levels<-"),
zzz.R:assign("log", function(x, base=exp(1)) UseMethod("log"),
zzz.R:assign("names<-", function(x, value) UseMethod("names<-"),
zzz.R:assign("rep", function(x, ...) UseMethod("rep"), envir = .GenericArgsEnv)
zzz.R:assign("round", function(x, digits=0) UseMethod("round"),
zzz.R:assign("seq.int", function(from, to, by, length.out, along.with, ...)
zzz.R:assign("signif", function(x, digits=6) UseMethod("signif"),
zzz.R:assign("trunc", function(x, ...) UseMethod("trunc"), envir = .GenericArgsEnv)
zzz.R:#assign("xtfrm", function(x) UseMethod("xtfrm"), envir = .GenericArgsEnv)
zzz.R:assign("as.numeric", get("as.double", envir = .GenericArgsEnv),

Bad function assignation

TypeScript is actually protecting you against doing something silly here, but it's doing so in a confusing way.

Let's reduce your example to just the interfaces A and B.

interface A {
f1( ) : void;
}

interface B extends A {
f2( ) : void;
}

type expectA = ( a: A ) => void;
type expectB = ( b: B ) => void;


function testA( a: A ) {
a.f1();
}

function testB( b: B ) {
b.f1();
b.f2();
}


const v1: expectA = testA;
const v2: expectA = testB; // error: Property 'f2' is missing in type 'A' but required in type 'B'
const v3: expectB = testB;
const v4: expectB = testA;

On first glance, the result at the bottom where only v2 has an error might seem counterintuitive. If B extends A, then why can't you use B everywhere you can use A?

The answer is because we're dealing with functions here. Look closely at the implementation of testB(). It calls the property b.f2() because it expects that its parameter b will have that property. But the left hand side of const v2: expectB is equivalent to the type (a: A) => void. The parameter of type A does not have f2(). So we're telling TypeScript conflicting things about what the proper type of v2 is; either it's a function that has a: A in which case a.f2() is not safe to call, or it's a function that has b: B in which case it is safe. It's a paradox!

(Keep in mind that this has nothing to do with whether testB actually tries to call b.f2(); the point is that it could, based on how its argument types are set, which would cause a runtime error in the v2 scenario.)

Now for const v4 you say you think it's "abnormal" that this would be OK, but if we look carefully at the functions again we can see that it makes sense that it is OK. If you passed either a type A or B variable to testA(), there are no possible errors because it will never try to access the f2() property.

Note also that extends does not work in TypeScript quite the way you might expect it to. Writing interface B extends A simply says that B will inherit all the properties of A. It does not establish any kind of relationship allowing B to stand in for any instance of A. Such behavior is called "polymorphism" and to do that you would need to use classes instead, e.g. class B extends A implements A.

class A {
foo = '';
}

class B extends A implements A {
bar = '';
}

let isA = new A();
isA = new B(); // this is fine because B implements A
let isB = new B();
isB = new A(); // error: Property 'bar' is missing in type 'A' but required in type 'B'

assigning the parameter to a new value inside the function, good or bad practice?

I think you should be differentiating from is it safe? and is it a good practice?

  1. Is reassigning a local variable safe? Yes, it is.
  2. Is reassigning a (local) variable a good practice? Not necessarily.

Reassigning variables is error prone and compromises readability of your program. There are a lot of style guide that explicitly forbid it because:

  • Prevents you from accessing the original value assigned to that variable
  • Creates confusion

My personal opinion is that you could simply treat every variable as not-to-be-reassigned, this would just make your program clearer and therefore reduce the risk of unintended behaviour.


Assignment to variables declared as function parameters can be misleading and lead to confusing behavior, as modifying function parameters will also mutate the arguments object. Often, assignment to function parameters is unintended and indicative of a mistake or programmer error.

https://eslint.org/docs/rules/no-param-reassign


also read:

  • https://eslint.org/docs/2.0.0/rules/no-shadow

Assignment expression in while condition is a bad practice?

There should be no performance problems with it (arguably, indexing with prefix increment can be slightly slower than postfix increment, due to issues with CPU pipelines; this is a microoptimization so ridiculously micro that it almost certainly means nothing in the context of JS engine overhead, even in C the compiler is likely to reorder expressions if it can to ensure it's not stalled waiting on the increment).

Either way, the main argument against assignment in a conditional is basically that most of the time when you do it, it's a mistake (you meant == or in JS, ===). Some code checkers (and C# requires this as a language feature to avoid accidents) are satisfied if you wrap the assignment in an additional layer of parens, to say, "Yup, I really meant to assign" (which is also necessary when you're comparing the result of the assignment to some other value; omitting the parens would instead compare, then assign a boolean, which even more likely to be wrong).

Some people have a hate on for increment/decrement operators used as part of larger expressions, because remembering order of operations is hard I guess, and because C programmers have been known to write horrible things like ++*++var and the like. I ignore these people; just don't use it for excessively tricky things.

Assigning function output to many variables: good or bad practice

If you're returning a,b,c,...,i, that's a bit excessive, though in general this isn't (necessarily) a poor design choice. In reality, this is the product of two Python features:

  • From the return statement, Python is interpreting/ treating the return type as a tuple that, syntactically, has been expanded, rather than treating it as the union of however many variables individually. That is, you could equivalently define a tuple tuple = (a,...,i) and return that.
  • Then again when you assign the a, ..., i = step1(), you're again expanding this tuple.

What you might consider is treating it as a tuple, ie, a single object, rather than 9 individual items, since it doesn't seem that you need all of them at once. This might be better for code style, etc.

Since you make the distinction that these a,...,i are "complex data" items, though you have given little about your particular case, it's probably best to split up this functionality to make its scope as clear, tight, and therefore manageable as possible. If I was working with you, I'd probably implore you to do so before I'd write any unit tests for it.

Assignment using get() and paste()

You would be better off saving these items in a list.

myList <- list()
myList[[paste("a","bis",sep=".")]] <- rep(NA,5))

or

myList[[paste(country[j],"ext",sep=".")]] <- data.frame(Year=rep(unique(get(country[j])$Year),each=24),
Age=rep(c(0,1,seq(5,110,5)),length(unique(get(country[j])$Year))),
mx=NA,qx=NA,lx=NA,Lx=NA,Tx=NA,ex=NA))

This relieves you from the pains of get() and assign() and also puts your data in nice structure for looping / applying.



Related Topics



Leave a reply



Submit