image.png
R 语言是一种函数式编程语言(FP), 函数在R中是一等公民,可以对函数做任何事情,就跟向量一样:你可以将它们赋予给变量,将它们存储在列表里,把它们作为其它函数的参数进行传递,在函数内部创建它们,甚至把它们作为一个函数的返回结果

First-class citizen: In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities.

为什么要函数式编程

为了防止错误,以及创建更灵活的代码,请采用”不要重复自己”(“do not repeat yourself“)的原则。
函数式编程工具是有价值的,因为它们提供了工具来减少重复
重复会使代码变得脆弱:它很容易引入错误,并且难以适应不断变化的需求。 要删除这个重复的来源,可以利用一个函数式编程技巧:把函数存储在列表里

  1. summary <- function(x) {
  2. funs <- c(mean, median, sd, mad, IQR)
  3. lapply(funs, function(f) f(x, na.rm = TRUE))
  4. }

匿名函数

在 R 中,函数也是对象。 它们不是自动绑定到一个名称的。 与许多语言(比如 C,C++, PythonRuby)不同, R 语言没有创建命名函数的特殊语法:当你创建一个函数时,使用常规的赋值运算符给它一个名字即可。 如果你选择不为函数指定一个名称,那么你会得到一个匿名函数。 像 R 语言中的所有函数一样, 匿名函数也有形式参数 formals(), 函数体 body()以及父环境 environment()

  1. lapply(mtcars, function(x) length(unique(x)))
  2. Filter(function(x) !is.numeric(x), mtcars)
  3. integrate(function(x) sin(x) ^ 2, 0, pi)

匿名函数的调用

使用以下方式进行调用

匿名函数一般写在一行上,并且不需要使用{}

(function(args) ...)(args)

  1. (function(x,y) x + y)(x =3, y =6)
  2. #[1] 9

匿名函数的最常见的用途之一是创建闭包——由其它函数创建的函数

闭包

“对象是带有函数的数据。 闭包是带有数据的函数。 “ ——John D. Cook

匿名函数的一种应用,是创建不值得命名的小函数。 另一个重要用途是创建闭包——函数创建的函数因为闭包被封闭在父函数的环境之中,并且可以访问父函数的所有变量,所以取了这个名字。 下面的内部function(x)就是闭包(squarecube

  1. power <- function(exponent){
  2. function(x){
  3. x^exponent
  4. }
  5. }
  6. # 在父函数(power())中,创建了两个子函数(square()和 cube())
  7. square <- power(2)
  8. square(2)
  9. cube <- power(3)
  10. cube(2)

查看一下闭包的封闭环境,可以看到两个闭包的封闭环境是不一样的

  1. as.list(environment(cube))
  2. as.list(environment(square))
  3. $exponent
  4. [1] 3
  5. $exponent
  6. [1] 2

闭包的父环境的是创建它的函数的执行环境, 执行环境通常在函数返回一个值后就会消失。 然而,函数会捕获它们的封闭环境。 这意味着,当函数 a 返回了函数 b,函数 b 捕获并存储了函数 a 的执行环境,则该执行环境不会消失
在 R 语言中,几乎所有的函数都是闭包。 所有的函数都会记录它被创建时所处的环境,通常,

  • 自己编写的的函数,那么是全局环境;
  • 包的函数,那么是包的环境。 唯一例外的是,直接调用 C 语言代码的原语函数,它们没有关联的环境

    函数工厂

    函数工厂(function factory)是创建新函数的是一种要素。 例如前面的power()函数就是一个函数工厂,可以根据不同的输入参数生成不同的函数

    可变状态

    父函数和子函数两层)拥有变量,可以让你在函数调用之间维护状态。 这是有可能的,因为每当执行环境刷新的时候, 封闭环境都是保持不变的。 在不同层次管理变量的关键,是双箭头赋值操作符(<<-)。 普通的单箭头赋值
    操作符(<-),总是在当前环境下进行赋值,与它不同的是,双箭头操作符将继续沿着父环境链进行查找,直到找到一个匹配的名字。 静态(static)父环境和<<-联合在一起,可以维护函数调用之间的状态。
    ```r count_n <- function(x){ i <- 0 function(x){ i <<- i + 1 i } } counter_1 <- count_n(0) counter_1(0) counter_1(0)

[1] 1 [1] 2

count_n <- function(x){ i <- 0 function(x){ i <- i + 1 i } } counter_1 <- count_n(0) counter_1(0) counter_1(0) counter_1(0)

[1] 1 [1] 1 [1] 1

  1. <a name="tqPaQ"></a>
  2. ## 函数列表
  3. 在 R 语言中,函数可以存储在列表里。 这使得它可以把相关的函数更容易地组织在一起,就像数据框把相关的向量组织在一起一样<br />例如,比较不同计算均值的函数, 调用列表内的函数只用`list$function_name(...)`
  4. ```r
  5. computer_mean <- list(
  6. base = function(x) mean(x),
  7. sum = function(x) sum(x) / length(x),
  8. manual = function(x){
  9. total = 0
  10. for(i in seq_along(x)){
  11. total <- total + x[i]
  12. }
  13. total / length(x)
  14. }
  15. )
  16. computer_mean$manual(1:23)
  17. computer_mean$base(1:23)
  18. computer_mean$sum(1:23)
  19. #[1] 12
  20. #[1] 12
  21. #[1] 12

要调用每个函数(例如,检查它们是不是都返回相同的结果),可以使用lapply()。由于没有内置函数来处理这种情况,所以我们需要一个匿名函数或者一个新的命名函数。结果会存在一个list之中

  1. lapply(computer_mean, function(func) func(c(1:10)))
  2. $base
  3. [1] 5.5
  4. $sum
  5. [1] 5.5
  6. $manual
  7. [1] 5.5

另一个使用函数列表的例子是对一个对象使用多种汇总方式。 要做到这一点,我们可以把每个汇总函数存储在一个列表里,然后用 lapply()运行它们

  1. x <- runif(10e4)
  2. sumarrise_data <- list(
  3. mean = mean,
  4. median = median,
  5. sum = sum,
  6. sd = sd
  7. )
  8. lapply(sumarrise_data, function(func) func(x))
  9. $mean
  10. [1] 0.4995992
  11. $median
  12. [1] 0.5013926
  13. $sum
  14. [1] 49959.92
  15. $sd
  16. [1] 0.2891104

如果需要传入其他的参数,可以在lapply()函数内指定,

  1. lapply(sumarrise_data, function(func) func(x, na.rm = T))