1 一个例子
- 格式:myfuntion<-function(arg1,arg2,…){
- statements
- return(object)
- }
- > mystats<-function(x,parametric=TRUE,print=FALSE){
- if (parametric) {
- center<-mean(x);spread<-sd(x)
- } else {
- center<-median(x);spread<-mad(x)
- }
- if (print & parametric) {
- cat("mean=",center,"\n","sd=",spread,"\n")
- } else if (print & !parametric) {
- cat("median=",center,"\n","\bmad=",spread,"\n")
- }
- result<-list(center=center,spread=spread)
- return(result)
- }
1) 函数体:body(),函数的代码
2) 形式参数列表:formals(),控制函数调用的参数列表
3) 环境:environment(),函数的变量所在位置的“地图”
- > f<-function(x) x^2
- > f
- function(x) x^2
- > formals(f)
- $x
- > body(f)
- x^2
- > environment(f)
- <environment: R_GlobalEnv>
注意:函数都有上述三个组成部分,有一个例外:原语函数(如:sum等),它是直接用.Primitive()调用C语言代码,并且不包含R语言代码。因此它们的formals()、body()、environment()都是NULL
词法作用域进行符号的值的查找,是基于在函数创建时是如何嵌套的,而不是它们
在调用时如何嵌套的。 有了词法作用域,你不需要知道函数是怎么被调用的,以及在哪里查找变量的值。你只需要看看函数的定义即可。
2)四个基本原则:
名字屏蔽、函数和变量、全新的开始状态、动态查找
(1)名字屏蔽:
a)如果一个名字在函数中没有定义,那么R语言将向上一个层次查找。
b)如果一个函数内部定义了另一个函数,也适用同样的规则: 首先,查看当前函数的内部,然后是这个函数被定义的环境,然后继续向上,以此类推,一直到全局环境,然后,再查找其它已经加载的包
c)同样的规则也适用于闭包——由其它函数创建的函数
例子:
- a)
- > x<-2
- > g<-function(){
- y<-1
- c(x,y)
- }
- >
- > g()
- [1] 2 1
- b)
- > x<-1
- > h<-function(){
- y<-2
- i<-function(){
- z<-3
- c(x,y,z)
- }
- i()
- }
- >
- > h()
- [1] 1 2 3
- c)
- > j<-function(x){
- y<-2
- function(){
- c(x,y)
- }
对于函数,规则有一点点调整。 如果很明显你要的是函数(例如,f(3)),那么在这样的语境中,R语言在搜索时,将忽略那些不是函数的对象。
- > l<-function(x) x + 1
- > m<-function(){
- l<-function(x) x * 2
- l(10)
- }
- > m()
- [1] 20
(3)全新的开始状态
函数每次调用时,一个新的环境就会被创建出来,随后函数会在该环境中执行。函数无法报告它上一次被调用时发生了什么,因为每次调用都是完全独立的。
- > j<-function(){
- if(!exists("a")){
- a<-1
- } else {
- a<-a + 1
- }
- print(a)
- }
- >
- > j()
- [1] 1
- > j()
- [1] 1
- 说明:每次调用时,都会创建一个先的环境,a都不存在
(4)动态查找
词法作用域决定了去哪里查找值,而不是决定在什么时候查找值。 R语言在函数运行时查找值,而不是在函数创建时查找值。这意味着,函数的输出是可以随着它所处的环境外面的对象,而发生变化的。
- <span lang="EN-US" style="font-size:12.0pt;font-family:
- "Lucida Console";mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
- background:#E1E2E5;mso-font-kerning:0pt">> f<-function()x
- <span lang="EN-US" style="font-size:12.0pt;font-family:
- "Lucida Console";mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
- background:#E1E2E5;mso-font-kerning:0pt">> x<-15
- <span lang="EN-US" style="font-size:12.0pt;font-family:
- "Lucida Console";mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
- background:#E1E2E5;mso-font-kerning:0pt">> f()
- <span lang="EN-US" style="font-size:12.0pt;font-family:
- "Lucida Console";mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:black;
- background:#E1E2E5;mso-font-kerning:0pt">[1] 15
- <span lang="EN-US" style="font-size:12.0pt;font-family:
- "Lucida Console";mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
- background:#E1E2E5;mso-font-kerning:0pt">> x<-20
- <span lang="EN-US" style="font-size:12.0pt;font-family:
- "Lucida Console";mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
- background:#E1E2E5;mso-font-kerning:0pt">> f()
- <span lang="EN-US" style="font-size:12.0pt;font-family:
- "Lucida Console";mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:black;
- background:#E1E2E5;mso-font-kerning:0pt">[1] 20
- <p class="MsoNormal" style="margin-left:31.5pt;text-indent:-31.5pt;mso-char-indent-count:
- -3.0"><span style="font-family:宋体;mso-ascii-font-family:Calibri;mso-ascii-theme-font:
- minor-latin;mso-fareast-font-family:宋体;mso-fareast-theme-font:minor-fareast;
- mso-hansi-font-family:Calibri;mso-hansi-theme-font:minor-latin">说明:环境不同函数中的对象值也不同。这不同于对象在函数体内,若对象在函数体内,每次执行函数都会产生一个新的环境,函数体中的变量将会重置;若对象在函数体外,执行函数时全局变量环境没有改变,所以还是会受到全局环境的影响。
4 所有的操作都是函数调用
万事万物都是对象;发生的所有事情都是函数调用。
- 如下两个表达等价:
- > x<-10
- > y<-5
- > x+y
- [1] 15
- > `+`(x,y)
- [1] 15
- > for(i in 1:2) print(i)
- [1] 1
- [1] 2
- > `for`(i,1:2,print(i))
- [1] 1
- [1] 2
- > sapply(1:5,`+`,3)
- [1] 4 5 6 7 8
- > sapply(1:5,"+",3)
- [1] 4 5 6 7 8
- 说明:第一个是称为+的对象的值,第二个是一个包含字符+的字符串。 第二个版本可以起作用,是因为lapply可以输入一个函数的名称而不是函数本身:如果你读过lapply函数的代码,那么你可以看到第一行使用了match.fun()函数通过名字来找到函数。
- > x <- list(1:3, 4:9, 10:12)
- > sapply(x, "[", 2)
- [1] 2 5 11
- > sapply(x, function(x) x[2])
- [1] 2 5 11
当调用一个函数时,你可以通过参数的位置,或者通过完整的名称或者部分的名称,来匹配参数。 参数匹配的顺序是:首先是精确的名称匹配(完美匹配),然后通过前缀匹配,最后通过位置匹配。
- > f<-function(abcdef,bcde1,bcde2){
- + list(a=abcdef,b1=bcde1,b2=bcde2)
- + }
- > str(f(1,2,3))
- List of 3
- $ a : num 1
- $ b1: num 2
- $ b2: num 3
- > str(f(2,3,abcdef=1))
- List of 3
- $ a : num 1
- $ b1: num 2
- $ b2: num 3
- > str(f(2,3,a=1))
- List of 3
- $ a : num 1
- $ b1: num 2
- $ b2: num 3
- > str(f(1,3,b=1))
- Error in f(1, 3, b = 1) : argument 3 matches multiple formal arguments
一般来说,你只希望使用位置匹配来匹配排在前面的一、两个参数;它们是最常用的,大多数用户都知道它们是什么。要避免使用位置匹配来匹配较少使用的参数,并且仅使用具有可读性的缩写形式来对参数进行部分匹配。 (如果你正在为一个包编写代码,你想要把它发布在CRAN上,那么不能使用部分匹配,而必须使用完整的名字。) 命名的参数应该总是排在未命名的参数后面。 如果一个函数使用了...,则...之后列出的参数都必须使用它们的全名。
2)给定一个参数列表来调用函数
- > args<-list(1:10,na.rm=TRUE)
- > do.call(mean,args)
- [1] 5.5
(a)函数定义的时候设定初始值
- > f<-function(a=1,b=2){
- c(a,b)
- }
- > f()
- [1] 1 2
(b)默认参数由另一个参数创建
- > g<-function(a=1,b=a*2){
- c(a,b)
- }
- > g(,2)
- [1] 1 2
- > g(10)
- [1] 10 20
(c)默认参数在函数内部创建的变量来定义
- > h<-function(a=1,b=d){
- d<-(a + 1) ^ 2
- c(a,b)
- }
- > h()
- [1] 1 4
- > h(10)
- [1] 10 121
默认参数在函数内部进行计算。 这意味着,如果表达式依赖于当前环境,那么结果将是变化的,取决于你是否使用了默认值或显式地提供了一个值。
- > f<-function(x=ls()){
- a<-1
- x
- }
- > f()
- [1] "a" "x"
- > f(ls())
- [1] "add" "adders" "f"
- 说明:在全局环境下:add、adders和f已经存在。
4)延迟计算
默认情况下,R语言函数的参数是延迟计算的。仅当实际用到这些参数的时候,它们才会被计算出来。
- > f <- function(x){
- 10
- }
- > f(stop("This is an error!"))
- [1] 10
- > f <- function(x){
- force(x)
- 10
- + }
- > f(stop("This is an error!"))
- Error in force(x) : This is an error!
- 说明:第一段代码没有用到x参数,所以没有报错;第二段代码force了一下,就算不用到也执行了,由于调用函数的时候没有对x赋值,所以报错了。
延迟性在if中的作用:
以下的第二个语句将仅在第一个语句为TRUE时才会被计算。如果不是这样的话,那么该语句会返回一个错误,因为NULL > 0是一个长度为0的逻辑向量,而不是if的一个有效输入。
- > x<-NULL
- > if(!is.null(x)&&x>0){}
大多数R中的函数是前缀操作符:函数的名称排在参数的前面。你还可以创建中缀函数,函数名位于它的参数之间,比如+或-。所有用户创建的中缀函数都必须以%开始和结束,R预定义了这些中缀函数:%%、%*%、%/%、%in%、%o%、%x%。
不需要%的内置中缀操作符的完整列表为:
::, :::, $, @, ?, *, /, +, -, >, >=, <, <=, ==, !=, !,&, &&, |, ||, ~, <-, <<-
- > `%+%` <- function(a, b) paste(a, b, sep = "")
- > "new" %+% " string"
- [1] "new string"
- 说明:在创建函数时,你必须把这个名字放在重音符"`"里面,因为它是一个特殊的名字。
中缀函数的名字比普通R函数更加灵活:它们可以包含任何字符序列(当然,除了%以外)。 你需要在定义函数的时候,对任何特殊字符进行转义,而不是在调用的时候进行转义
- > `% %` <- function(a, b) paste(a, b)
- > `%'%` <- function(a, b) paste(a, b)
- > `%/\\%` <- function(a, b) paste(a, b)
- > "a" % % "b"
- [1] "a b"
- > "a" %'% "b"
- [1] "a b"
- > "a" %/\% "b"
- [1] "a b"
替换函数的行为表现得好像它们可以就地修改(译者注:modify in place,即修改立即生效,直接作用在被修改的对象上)参数,并且它们都拥有特别的名字xxx<-。 它们通常有两个参数(x和值),虽然它们可以有更多参数,但是它们必须返回修改过的对象。 例如,下面的函数允许你修改向量的第二个元素:
- > `second<-`<-function(x,value){
- x[2]<-value
- x
- }
- > x<-1:10
- > second(x)<-5
- > x
- [1] 1 5 3 4 5 6 7 8 9 10
当R计算赋值语句second(x) <- 5时,它注意到左手边的<-不是一个简单的名称,因此它寻找一个命名为second<-的函数来进行替换操作。
注意:我之所以说它们"表现得好像"可以就地修改参数,是因为实际上它们创建了一个修改后的副本。 我们可以使用pryr::address()来查看,找到底层对象的内存地址。
- > library(pryr)
- > x<-1:10
- > address(x)
- [1] "0x165f4f90"
- > second(x)<-6
- > address(x)
- [1] "0x1b2c4b10" #地址不同
把替换和取子集操作结合起来:
- > x<-c(a=1,b=2,c=3)
- > names(x)
- [1] "a" "b" "c"
- > names(x)[2]<-"two"
- > names(x)
- [1] "a" "two" "c"
- 相当于写了如下代码(R实质):
- `*tmp*` <- names(x)
- `*tmp*`[2] <- "two"
- names(x) <- `*tmp*`
7 返回值1)语法:
retrun()语句
f <-function(x, y) {
if (!x) return(y)
# 这里是复杂的处理过程
}
2)保护机制
R语言提供了保护机制,让你避免一类副作用:大多数R对象具有修改时复制(copy-on-modify)的语义。所以修改函数参数不会改变原始值:
- > f<-function(x){
- x$a<-2
- x
- }
- > x<-list(a=1)
- > f(x)
- $a
- [1] 2
invisible(x)
强制可见:(a<-2)
8 退出时执行
on.exit()
注意:如果你在一个函数中调用多个on.exit()函数,那么请务必设置add = TRUE。 不幸的是,在on.exit()中add的默认值是add = FALSE,这样每次你运行它的时候,它都会覆盖已有的退出(exit)表达式。 由于on.exit()的特殊实现方式,因此无法创建一个默认设置为add = TRUE的变种函数,所以使用它的时候必须小心。



雷达卡



京公网安备 11010802022788号







