https://github.com/rsharp-lang/R-sharp

R#语言的语法主要来自于R语言,其在保留了很多的R语言特性的同时,也添加了很多新语法特性。对于R#与R语言之间相同的语法特性,在本简明教程中我就不再叙述了,在这里主要是给大家说明一下R#语言相对于R语言新增的一些语法特性。

R#语言中的向量

R#语言任然保持着和其前辈R语言一样的向量化编程的特性。对于声明一个向量,在R语言之中,需要使用c函数进行申明,而对于R#语言而言,除了使用c函数,还可以直接使用方括号进行申明,例如:

x = c("A","B","C")
# x
# [1]     "A" "B" "C"
x = ["A","B","C"]
# x
# [1]     "A" "B" "C"

R#语言中的函数声明

对于R语言而言,函数其实就是一个closure类型的变量,在R之中使用function函数进行closure的申明,并赋值给一个符号。下面就是R的文档中对function函数的帮助信息:

function {base}

Function Definition

Description
These functions provide the base mechanisms for defining new functions in the R language.
Usage
function( arglist ) expr
Arguments

  • arglist Empty or one or more name or name=expression terms.
  • expr An expression.
    Details
    The names in an argument list can be back-quoted non-standard names (see ‘backquote’).

If value is missing, NULL is returned. If it is a single expression, the value of the evaluated expression is returned. (The expression is evaluated as soon as return is called, in the evaluation frame of the function and before any on.exit expression is evaluated.)

If the end of a function is reached without calling return, the value of the last evaluated expression is returned.

但是在R#语言之中,function这个单词将作为一个关键词存在,而函数的申明语法在基本与R保持着一致的同时也添加了一些VisualBasic的语法特性,例如在R#脚本中声明一个函数,可以有下面的语法:

  1. 下面的代码是一个申明函数的完整语法,在R#中申明一个函数需要一个符号名称,例如name,然后将这个符号的类型通过以VisualBasic类似的语法申明为一个具有某种功能的函数类型:
let name as function( arglist ) {
    # ...
}
  1. 但是在R与R#都是函数式编程语言,在向其他的函数传递函数值类型的函数参数的时候,使用上面的完整语言进行申明参数肯定不方便。所以在R#语言之中还保留着与R语言一样的函数申明方法。但是这个时候在R#之中申明的是一个匿名函数,例如
exec = function(f) print(f())
exec(function() x)
# [1]     "A" "B" "C"

function() x
# declare function '$<$anonymous_01008>'() {
    # function_internal
    # &x;
# }

在R#中执行上面的代码,发现虽然实现的功能与在R语言中是一样的。但是函数的申明的输出却差异很大。产生这个差异的原因就是R#语言将function作为一个关键词,而非像R语言一样作为一个函数名来使用。因为在R#之中,任何对象都要有一个符号名称,对于诸如function() x这样的申明形式,因为没有对应的符号名称,所以R#环境自动为这个值分配了一个匿名符号。

R#语言中的lambda函数

对于使用R#语言进行函数式编程,可以使用上面所提到的匿名函数进行,也可以使用R#语言所特有的lambda函数进行,例如下面的两个函数式编程的操作的效果几乎是一样的(为什么我要在这里特异的强调几乎这个修饰词,lambda函数与匿名函数之间的差异我们将在下面聊):

exec = function(x, f) print(f(x));
exec(0:4, function(x) x + 1);
# [1]     1 2 3 4 5
exec(0:4, x -> x + 1);
# [1]     1 2 3 4 5

嗯嗯嗯,两者之间的效果确实是几乎一模一样的,那如果是下面的例子呢?

y    = 9;
exec = function(x, f) f(x) + y;

(function() {
    const y = 1;
    print(exec(1, function(x) x + y));
    # [1] 11
})();
(function() {
    const y = 1;
    print(exec(1, x -> x + y));
    # [1] 19
})();

好了,差异看起来非常明显了!为什么会出现这种差异?这是因为呀,匿名函数其是一个closure闭包,closure就意味着匿名函数有自己的上下文environment,因为在匿名函数的上下文中,y符号的值是1,所以最终的结果是:

# (function(x) x + [closure]y) + [global]y
               1 +          1  +         9 = 11

很明显,在匿名函数的例子中,两个y符号是不同的对象;但是对于lambda函数呢?lambda函数其本质上只是一个表达式,不是一个closure闭包,所以lambda函数相对于匿名函数而言,其更加的轻量化,内存负载更低。因为其并不会像匿名函数一样具备有自己的上下文environment,所以其结果为:

# (x -> x + [global]y) + [global]y
        1 +         9  +         9 = 19

但是大家在编写R#脚本的时候,只要是不涉及到闭包上下文环境的切换的情形,lambda函数仍然是可以看作与匿名函数等价的,所以大家任然可以尽情的在lapply或者sapply这类R语言中常用的向量化编程函数中使用lambda函数。

R#语言中的管道符号

在R语言之中,大家可以使用dplyr,或者magrittr包中所提供的%>%操作符实现管道操作。在R#语言之中,大家则可以直接使用一个原生的管道操作符|>,例如:

bitmap(file = logo.png) {
    seq.fasta
    |> read.fasta
    |> MSA.of
    |> plot.seqLogo(title)
    ;
}

R#语言中的字符串插值

在传统的R脚本之中,字符串的插值是通过paste函数来实现的,而在R#语言之中,则可以通过类似于TypeScript语言的字符串插值语法来一步到位的实现,更加的简洁明了,例如:

paste("Hello" , c("word", "world"), "!")
# [1] "Hello word !"  "Hello world !"

# new language feature in R# language
`Hello ${["word", "world"]}!`
# [1] "Hello word!"  "Hello world!"

上面所展示的新的字符串插值语法对比之前的paste函数是不是更加的方便和简洁明了?

R#语言的acceptor语法糖

我们在进行编程的时候,肯定是希望我们的代码可以更加的模块化,所以出现了诸如function之类的概念用于生成代码模块。但是在这里的模块化,是另外的一个概念,其更加的相当于一个功能组。这个功能组的概念在R#语言中为被称作为一种acceptor名字的语法糖,我们来看一个例子吧:

write.csv(file = "/path/to/table.csv") {
    print("blablabla");
    str(x);

    data.frame(a = x, b = TRUE);
}

通过上面的代码我们是不是很清楚的看到,实现某一个功能的代码块都放到了一个结构模块之中,我们的代码思路是不是更加清楚了。如果我们用传统的R脚本来实现上面的过程呢?

print("blablabla");
str(x);

z = data.frame(a = x, b = TRUE);
write.csv(z, file = "/path/to/table.csv");

嗯。。。前半段的print和str函数看起来好像和下面的data.frame没有那么大的关联了。但是实际上我们是希望将上面的print和str与下面的写表格文件的代码归组在一块的。

如何理解R#语言中的acceptor语法糖呢?acceptor语法糖其实就是管道操作的一个变种,如果我们将上面的acceptor语法糖例子分别改写回普通的函数调用或者管道模式,大家应该会非常清楚这个语法糖是怎么一回事了。一般而言,在R语言或者R#语言之中,任意一个代码逻辑块实际上都是一个表达式,所以我们可以编写出诸如下面的代码:

write.csv({
    print("blablabla");
    str(x);

    data.frame(a = x, b = TRUE);
}, file = "/path/to/table.csv");

因为打印数据和生成dataframe的操作这一组表达式都是在一个closure表达式内,构成一个closure表达式的,所以上面所展示的一组操作可以作为一个表达式正常的传递给write.csv函数的第一个参数。上面的写法,我们也可以改写为管道操作样式:

({
    print("blablabla");
    str(x);

    data.frame(a = x, b = TRUE);
}) |> write.csv(file = "/path/to/table.csv");
Latest posts by xie guigang (see all)

Attachments

  • R_ubuntu-bash • 212 kB • 13 click
    02.06.2021

  • LexA • 78 kB • 16 click
    02.06.2021

6 Responses

Leave a Reply

Your email address will not be published.