Variable scope

The outside environment can't peak into functions:

f1 <- function(a, b) {
  a + b
}

f1(10 , 20)
## [1] 30
a <- 11
a
## [1] 11
f1(10, 20)
## [1] 30

But functions can reach up to the outside:

c <- 100

f2 <- function(a, b) {
  a + b + c
}

f2(10, 20)
## [1] 130
c <- 200
f2(10, 20)
## [1] 230
c  <- 100
f3 <- function(d) {
  function(a, b) {
    a + b + c + d
  }
}

In this case our f3 is a functin that returns a function (so meta).

f3(10)
## function(a, b) {
##     a + b + c + d
##   }
## <environment: 0x4efa8f8>
f4 <- f3(1)
f4(10, 20)
## [1] 131
f5 <- f3(2)
f5(10, 20)
## [1] 132

This would be an equivalent of the last line (even if it is a bit iffy):

f3(2)(10, 20)
## [1] 132

Pass-by-value and immutability

Let's see it in action:

c  <- 100
f6 <- function(a, b) {
  print(c)
  c <- 200
  print(c) 
  
  a + b + c
}
f6(10, 20)
## [1] 100
## [1] 200
## [1] 230
c
## [1] 100

Here's an example that might be surprising for folks who have worked in other dynamic languages:

l  <- list(a = 10, b = 20)
f7 <- function(mutantList) {
  mutantList$c <- 30
  
  mutantList
}
f7(l)
## $a
## [1] 10
## 
## $b
## [1] 20
## 
## $c
## [1] 30
l
## $a
## [1] 10
## 
## $b
## [1] 20

And now for something crazy: being lazy

area <- function(l, w = l) {
  l * w
}
area(2, 3)
## [1] 6
area(2)
## [1] 4

Python would not have been cool with that.

So you can do things like this:

area <- function( l
                , w = if (square) { l }
                , square = if (l == w) { TRUE } else { FALSE } 
                ) {
  
  if (square) { print("It's a square!") }
  
  l * w
}
area(2, 3)
## [1] 6
area(2, 2)
## [1] "It's a square!"
## [1] 4
area(2, square = TRUE)
## [1] "It's a square!"
## [1] 4

This is either really awesome or totally insane, depending on perspective.