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
“)的原则。
函数式编程工具是有价值的,因为它们提供了工具来减少重复
重复会使代码变得脆弱:它很容易引入错误,并且难以适应不断变化的需求。 要删除这个重复的来源,可以利用一个函数式编程技巧:把函数存储在列表里
summary <- function(x) {
funs <- c(mean, median, sd, mad, IQR)
lapply(funs, function(f) f(x, na.rm = TRUE))
}
匿名函数
在 R 中,函数也是对象。 它们不是自动绑定到一个名称的。 与许多语言(比如 C,C++, Python
和 Ruby
)不同, R 语言没有创建命名函数的特殊语法:当你创建一个函数时,使用常规的赋值运算符给它一个名字即可。 如果你选择不为函数指定一个名称,那么你会得到一个匿名函数。 像 R 语言中的所有函数一样, 匿名函数也有形式参数 formals(), 函数体 body()以及父环境 environment()
lapply(mtcars, function(x) length(unique(x)))
Filter(function(x) !is.numeric(x), mtcars)
integrate(function(x) sin(x) ^ 2, 0, pi)
匿名函数的调用
使用以下方式进行调用
匿名函数一般写在一行上,并且不需要使用{}
(function(args) ...)(args)
(function(x,y) x + y)(x =3, y =6)
#[1] 9
匿名函数的最常见的用途之一是创建闭包——由其它函数创建的函数
闭包
“对象是带有函数的数据。 闭包是带有数据的函数。 “ ——John D. Cook
匿名函数的一种应用,是创建不值得命名的小函数。 另一个重要用途是创建闭包——函数创建的函数。 因为闭包被封闭在父函数的环境之中,并且可以访问父函数的所有变量,所以取了这个名字。 下面的内部function(x)
就是闭包(square
, cube
)
power <- function(exponent){
function(x){
x^exponent
}
}
# 在父函数(power())中,创建了两个子函数(square()和 cube())
square <- power(2)
square(2)
cube <- power(3)
cube(2)
查看一下闭包的封闭环境,可以看到两个闭包的封闭环境是不一样的
as.list(environment(cube))
as.list(environment(square))
$exponent
[1] 3
$exponent
[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
<a name="tqPaQ"></a>
## 函数列表
在 R 语言中,函数可以存储在列表里。 这使得它可以把相关的函数更容易地组织在一起,就像数据框把相关的向量组织在一起一样<br />例如,比较不同计算均值的函数, 调用列表内的函数只用`list$function_name(...)`
```r
computer_mean <- list(
base = function(x) mean(x),
sum = function(x) sum(x) / length(x),
manual = function(x){
total = 0
for(i in seq_along(x)){
total <- total + x[i]
}
total / length(x)
}
)
computer_mean$manual(1:23)
computer_mean$base(1:23)
computer_mean$sum(1:23)
#[1] 12
#[1] 12
#[1] 12
要调用每个函数(例如,检查它们是不是都返回相同的结果),可以使用lapply()
。由于没有内置函数来处理这种情况,所以我们需要一个匿名函数或者一个新的命名函数。结果会存在一个list
之中
lapply(computer_mean, function(func) func(c(1:10)))
$base
[1] 5.5
$sum
[1] 5.5
$manual
[1] 5.5
另一个使用函数列表的例子是对一个对象使用多种汇总方式。 要做到这一点,我们可以把每个汇总函数存储在一个列表里,然后用 lapply()
运行它们
x <- runif(10e4)
sumarrise_data <- list(
mean = mean,
median = median,
sum = sum,
sd = sd
)
lapply(sumarrise_data, function(func) func(x))
$mean
[1] 0.4995992
$median
[1] 0.5013926
$sum
[1] 49959.92
$sd
[1] 0.2891104
如果需要传入其他的参数,可以在lapply()
函数内指定,
lapply(sumarrise_data, function(func) func(x, na.rm = T))