定义和调用函数
R 中的函数是一个值,就像数字或字符串一样。你用 function 关键字创建它,再用 <- 把它绑定到一个名字上,和把其他任何东西存进变量的方式一样:
各个部分:function(x) 声明了一个名为 x 的参数,花括号里是函数体,调用 square(4) 就会在 x 设为 4 的情况下运行函数体。函数体只有一行时,花括号可以省略,square <- function(x) x^2 是同一个函数,但函数体变长之后,加上花括号更好读。
因为函数就是一个值,所以你对变量的了解全都适用:你可以重新给它赋值,把它传给另一个函数,或者把它存进列表。地道的 R 写法有一半都建立在这一点上,包括整个 apply 函数族。
返回值:最后一个表达式说了算
R 函数返回最后一个被求值的表达式所产生的值。没有必须写的 return 语句:
return() 确实存在,作用也和你想的一样:立即停止函数,并把它的参数作为结果返回。地道的 R 代码把它留给提前退出:当某个输入让后面的工作失去意义时,在函数开头就直接返回。它常和 if-else 判断搭配使用:
在最后一行写 return(a / b) 也没错,只是多余。把 return() 留给提前发生的退出,正常路径就交给最后一个表达式。
默认值和具名参数
参数可以带默认值,在参数列表中用 = 声明。满意默认值的调用者直接省略这个参数即可:
第三次调用展示了另一半内容:具名参数。当你在调用处写 name = "Ada" 时,位置就不再重要了:R 先按名称匹配,再按位置填充剩下的参数。由此可以得出两条实用规则:
- 必需参数放在前面,带默认值的参数放在后面,这样按位置调用依然自然。
- 在调用处,凡是从位置上看不出含义的参数都写上名字。
greet("Ada", punctuation = "?")比让读者去数逗号清楚得多。
用 ... 传递参数
特殊参数 ...(读作 “dots”)表示“任意数量的额外参数”。声明了 ... 的函数可以接受它从未列出的参数,并把它们转交给另一个函数:
shout() 不知道也不关心你传了多少个单词,它把它们全部交给 paste()。注意 punctuation 写在点参数之后:在 ... 之后声明的参数只能按名称设置,这正是选项类参数需要的效果。这种透传模式在 base R 中随处可见:sapply(x, round, digits = 2) 之所以能用,就是因为 sapply 通过它自己的 ... 把 digits = 2 转交给了 round。
点参数的代价是拼写错误会悄悄溜过去:把 punctuation 错拼成 punctation,它只会被吞进 ...,而不会报错。使用点参数的函数要保持简短。
匿名函数
当一个函数只用一次时,通常是作为另一个函数的参数,你可以完全不给它起名字:
两行做的是同一件事。function(x) x^2 是经典写法;\(x) x^2 是 R 4.1 新增的简写,其中 \ 代替了 function 这个词。两者除了打字量之外完全相同,简写并不是另一种函数。
当操作只有一行、而且只在一个调用处使用时,就用匿名函数。一旦你在两个地方粘贴了同一个匿名函数,或者它超过了一行,就给它起个名字:具名函数便于测试,本身也能说明用途。
作用域:函数能看到什么
R 使用词法作用域:当函数需要一个在自己的参数和局部赋值中找不到的变量时,它会去定义这个函数的地方所在的环境中查找,而不是调用它的地方。实际上,这意味着函数可以读取它周围脚本中的变量:
反过来说:函数内部的赋值永远不会泄漏出去。change_inside 内部的 rate <- 0.5 创建了一个全新的局部变量,函数返回时它就消失了;外面的 rate 打印出来仍然是 0.1。这是一项特性,函数不会意外破坏你的工作空间,但它会让以为内部赋值会保留下来的初学者感到意外。如果函数需要把结果交给外部,就把它返回,再在调用处赋值。(有一个能向外层赋值的 <<- 运算符;在你真正需要它之前,把它当作代码异味。)
一个完整的例子
下面是函数真正带来回报的模式:注意到自己在重复写同样的三行分析代码时,把它们包进一个函数,然后重复使用。比如你经常要汇总一些测量值向量:
本页的所有内容都在这里了:一个带默认值的 digits 参数,一次用具名参数覆盖它的调用,以及一个负责返回的最后表达式(round(out, digits))。这个函数读起来像一句话,适用于任何数值向量,当你决定加上中位数时,也只需要改一个地方。
要点总结
name <- function(args) { body }创建一个函数;最后一个被求值的表达式就是它的返回值。return()用于提前退出,而不是用在正常的最后一行。- 默认值在参数列表中用
=声明;具名参数让调用处不受顺序影响,也更易读。 ...接受并转交任意额外参数,功能强大,但会掩盖拼写错误。function(x)和\(x)是一回事;一次性的单行操作用匿名函数。- 函数从定义它的地方读取变量;函数内部的赋值只留在内部。
下一篇:apply 函数族,它们接收你写的函数,并把它应用到整个向量和列表上。
常见问题
在 R 中怎么创建函数?
把一个 function 对象赋值给一个名字:square <- function(x) { x^2 }。参数写在圆括号里,函数体写在花括号里,调用时写 square(4)。最后一个被求值的表达式就是函数的返回值。
R 中需要写 return() 吗?
不需要。R 函数会自动返回它最后求值的那个表达式的值。return() 是存在的,地道的 R 代码把它留给提前退出:当输入无效或者某个边界情况已经处理完时,在函数开头直接返回。
R 中的匿名函数是什么?
一个定义时不起名字的函数,通常直接传给另一个函数:sapply(x, function(v) v^2)。从 R 4.1 开始还有简写 \(v) v^2,意思完全一样,只是少打几个字。