ShoneSharp(S#炫语言)

导航

ShoneSharp语言(S#)的设计和使用介绍系列(8)— 最炫“公式”风

 

ShoneSharp语言(S#)的设计和使用介绍

系列(8)— 最炫“公式”风

作者:Shone

声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp。

摘要: S#公式是由各种操作数(常量、变量、或子公式)和操作符(算符、函数、属性、方法、或子公式)组合而成,公式和子公式可以形成复杂嵌套结构。S#还在公式级别提供了相当于其他语言语句级别的系统专用公式,使得S#公式表达能力超强,易用性也好,可以说是最为炫酷的公式表达风格。

软件: S#语言编辑解析运行器(ShoneSharp.13.6.exe),运行环境.NET4.0,单EXE直接运行,绿色软件无副作用。网盘链接https://pan.baidu.com/s/1nv1hmJn

公式是编程语言最基础的表达结构,所有语言都支持公式。各种语言的公式语法各不相同,表达能力也差别很大。比如LISP公式语法很简单,就是S表达式,但表达能力却非常强大,负面的影响的易用性不好。有的语言则语法比较好用,而表达能力有限。

S#语言的公式语法符号很多,表达能力非常强,易用性也不错,其综合能力在所有语言中也是领先的,可以说“S#带来了最炫的公式表达风格”。

对于上述主观论断,看好本博文后有不服者可以来辩。另外语言是相通的,其他语言公式的特性基本上S#都支持,因此其他语言爱好者看一眼本文,也可以提升对自己所学语言的领悟。

一、公式概念

公式是由各种操作数(常量、变量、或子公式)和操作符(算符、函数、属性、方法、或子公式)组合而成,可以解析并计算得到数据值。

由于公式的操作数和操作符都可能是另一个公式构成,因此一个公式可以包含非常复杂的嵌套结构,用来表达或计算各种更加复杂的数据。这一点和LISP语言非常相似。

S#公式中可以包含注释,注释分行注释//xxx和块注释/*xxx*/,使用方法与C#相同。

二、常量引用公式

常量是系统预先定义命名的数据值,在任何上下文中都是保持数据值不变。不同数据类型预定义了不同的常量,具体可以在软件的"所有"面板中查阅。

常量引用公式是直接引用常量名称,是最简单的公式之一,其计算结果就是系统预定义的数据值(如true、false、null等)。

三、变量引用公式

变量是用户自定义命名的数据值,必须先定义后引用。在不同上下文中用户可以定义同名变量,因此变量引用是依赖于上下文的。

变量的命名规则基本与C#相同,即字母或_开头的包含一道多个字母或数字或_的名字,并排除与系统预定义常量以及其他关键字重复的名字(包括para, eval, if, case, switch, each, eachx, for, forx, dup, dupx, iter, do, sum, prod, integal, load, parse, call, func, class, foreach, while, var, default, return, break, continue, in, else, true, false, null, local, parent, this, base, host, import, include等)。

变量引用公式是直接引用变量名称,也比较简单,但其计算结果需要在上下文中进行查找。上下文其实指的是变量堆栈链,S#的有些公式(下面会介绍)带有局部变量堆栈,允许定义变量,如果这些公式中又嵌套了其他带有局部变量堆栈的子公式,那么就会形成变量堆栈链。

变量引用默认会先在当前公式最近的局部变量堆栈查找,如果找不到就会到上一级的变量堆栈,一层层找直至找到为止(类似JavaScript)。看一下代码实例就明白了:

{

    a = 10  ,

    b = {

          c = 20 ,

          d = a + c   //d计算结果为30

        }

}

 

四、运算符号公式

运算符号(简称算符)是系统预定义的一些操作符符号,用于对操作数进行计算求值。其功能类似与函数调用,只不过写法上有指定的格式,如单目、双目、三目以及其他。

通常算符都是针对特定数据类型的,但有些算符可以处理多种类型,为便于理解下面从简单类型到复杂类型分开介绍。

4.1 数值算符  +  -  *  /  %  ^

数值算符用于对操作数进行数值计算,与其他语言不同,S#可以处理多种类型,另外操作数互换结果可能不同。举例如下:

-10, -[10,20], -{10,20,30}             //取负

10+3, [10,20]+3, {10,20,30}+3          //加法

10-3, [10,20]-3, {10,20,30}-3          //减法

10*3, [10,20]*3, {10,20,30}*3          //乘法

10/3, [10,20]/3, {10,20,30}/3          //除法

10%3, [10,20] %3, {10,20,30}%3         //取余

10^3, [10,20]^3, {10,20,30}^3          //乘方

 

4.2 布尔算符  <  >  <=  >=  ==  !=  !  &&  ||

布尔算符用于对操作数进行计算获得布尔值,基本用法与C#相同。举例如下:

10<20    //判断是否小于

10>20    //判断是否大于

10<=20   //判断是否小于等于

10>=20   //判断是否大于等于

10==20   //判断是否等于

10!=20   //判断是否不等于

!true                //判断条件取反

true && false        //并且判断,即两个条件是否都成立

true || false        //或者判断,即两个条件是否有一个条件成立

 

4.3 数组算符  [,]  ,  ~  []  .  $  #  &  |  .. .$ .~ .< .>

数组是包含同类型数据的一个集合,S#有专用于对数组进行构造和运算的算符。

[10, 20, 30, 40, 50]     //构造数组的最基本用法

10, 20, 30, 40, 50       //构造数组的简化用法,独立没有冲突时可以省略[]

~[10, 20, 30, 40, 50]    //数组元素反向重排,结果[50,40,30,20,10]

[10, 20, 30, 40, 50][2]  //索引数组元素,索引号从0开始,可以变量,结果30

[10, 20, 30, 40, 50].2   //索引数组元素,索引号从0开始,必须数字,结果30

 [10, 20, 30, 40, 50][2,3,4]  //离散索引多个数组元素,索引号从0开始,可以变量,结果[30,40,50]

[10, 20, 30, 40, 50][2:4]     //连续索引多个数组元素,索引号从0开始,可以变量,结果[30,40,50]

3$10, 2$[20,30]          //整体重复数组元素,结果[10,10,10,20,30,20,30]

3#10, 2#[20,30]          //交错重复数组元素,结果[10,10,10,20,20,30,30]

[10,20,30]&[1,2]         //整体插入数组元素,结果[10,1,2,20,1,2,30]

[10,20,30]|[1,2]         //交错插入数组元素,结果[10,1,20,2,30]

10..15           //构造连续范围数组,结果[10,11,12,13,14,15]

10..15.$2        //构造指定步长的连续范围数组,结果[10,12,14]

10..15.~2        //构造接近步长的等距范围数组,结果[10,11.666666666666666,13.333333333333332,14.999999999999998]

10..15.<2        //构造小于等于步长的等距范围数组,结果[10,11.666666666666666,13.333333333333332,14.999999999999998]

10..15.>2        //构造大于等于步长的等距范围数组,结果[10,12.5,15]

 

4.4 列表算符  {,}  ~  []  .  $$  ## $$$ ###  &  |  ... .$ .~ .< .>

列表是可以包含不同类型数据的一个集合,与数组类似,S#有专用于对列表进行构造和运算的算符。

列表与数组的使用原则:单层同类型尽量用数组,性能更好;多层次数据结构使用列表,表达能力更强。

{true, 20, 30, 40, 'hjx'}     //构造列表的最基本用法

~{10, 20, 30, 40, 50}         //数组元素反向重排,结果{50,40,30,20,10}

{true, 20, 30, 40, 'hjx'} [2]  //索引列表元素,索引号从0开始,可以变量,结果30

{true, 20, 30, 40, 'hjx'}.2    //索引列表元素,索引号从0开始,必须数字,结果30

{true, 20, 30, 40, 'hjx'} [2,3,4]  //离散索引多个列表元素,索引号从0开始,可以变量,结果{30,40,'hjx'}

{true, 20, 30, 40, 'hjx'} [2:4]    //连续索引多个列表元素,索引号从0开始,可以变量,结果{30,40,'hjx'}

2$${true, 10, 'hjx'} //整体重复列表元素,结果{true,10,'hjx', true,10,'hjx'}

2##{true, 10, 'hjx'} //交错重复列表元素,结果{True,True,10,10,'hjx','hjx'}

2$$${true, 10, 'hjx'}     //整体多重列表元素,结果{{true,10,'hjx'}, {true,10,'hjx'}}

2###{true, 10, 'hjx'}     //交错多重列表元素,结果{{True,True},{10,10},{'hjx','hjx'}}

{true, 10, 'hjx'}&{1,2}   //整体插入列表元素,结果{True,1,2,10,1,2,'hjx'}

{true, 10, 'hjx'}|{1,2}   //交错插入列表元素,结果{True,1,10,2,'hjx'}

10...15              //构造连续范围列表,结果{10,11,12,13,14,15}

10...15.$2           //构造指定步长的连续范围列表,结果{10,12,14}

10...15.~2          //构造接近步长的等距范围列表,结果{10,11.666666666666666,13.333333333333332,14.999999999999998}

10...15.<2          //构造小于等于步长的等距范围列表,结果{10,11.666666666666666,13.333333333333332,14.999999999999998}

10...15.>2          //构造大于等于步长的等距范围列表,结果{10,12.5,15}

 

4.5 数据表算符

数据表是包含一系列键值数据对的集合,注意与其他语言不同,数据表带有局部变量堆栈,其键就是堆栈中的变量名称,而值就是变量值,而且数据表的基类是数据表,这就意味着数据表也可以通过索引进行访问。

数据表可以很复杂,其中变量的作用范围可以有多种,后面将专题介绍。

{A=5, B=[1,2], C={10,20,30}}      //构造基本数据表

{A=5, B=[1,2], C={10,20,30}}['B'] //通过键值索引数据表元素,结果[1,2]

{A=5, B=[1,2], C={10,20,30}}.B    //通过属性访问数据表元素,结果[1,2]

{A=5, B=[1,2], C={10,20,30}}[1]   //通过列表索引数据表元素,结果[1,2]

{A=5, B=[1,2], C={10,20,30}}.1    //通过列表索引数据表元素,结果[1,2]

 

4.6 特殊算符  ()  <>  ??  ?  $  &  *

(10+20)              //用于对公式进行隔离计算,可以改变其优先级,结果15

(10, 20)             //构造二维点坐标

(10, 20, 30)         //构造三维点坐标

<10, 20>             //构造二维向量

<10, 20, 30>         //构造三维向量

null??10             //非空值计算符号,左侧值为空则取右侧值,结果10

?’(20+30)/2’     //对字符串解析并求值,结果25

$’c:\hjx.shone’  //打开字符串表示的文件,并对其内容解析并求值

&xxx             //获取右侧公式解析树节点的标记引用,类似C#中指针取地址

*xxx             //获取右侧公式解析树节点的结果值引用,类似C#中指针取值

 

五、面向对象公式

5.1 属性调用

属性调用是面向对象的表达方式之一,可以方便表达被调用对象直接暴露的相关信息,其基本格式是:对象.属性名称。

[10,20,30,40,50].Count           //获取数组的个数,结果5

注意属性调用优先查找被调用对象的局部变量堆栈,如果没有变量堆栈或找不到,就会去调用该对象类型的系统预定义属性(不同数据类型预定义了不同的方法,具体可以在软件"所有"面板中查阅),如果还没有则报错。例如:

{A=1,B=2}.Count                 //结果2

{A=1,B=2,Count=100}.Count       //结果100,注意优先调用Count变量而不是列表的属性Count。

 

理论上属性写法可以用函数代替(如count(xxx)),但属性写法更加简洁直观,可以形成很有特色的链式写法,如A.B.C….,一直点下去。

5.2 方法调用

方法也是面向对象的表达方式之一,可以方便表达针对被调用对象的各种操作,其基本格式是:对象.方法名称(参数,…)。

[10,20,30,40,50].Sub(2)          //从数组[10,20,30,40,50]的索引2位置开始提取子数组,结果[30,40,50]

注意方法调用也会优先查找被调用对象的局部变量堆栈,如果没有变量堆栈或找不到,就会去调用该对象类型的系统预定义方法(不同数据类型预定义了不同的方法,具体可以在软件"所有"面板中查阅),如果还没有则报错。例如:

{A=1,B=2}.Sub(1)                      //结果{2}

{A=1,B=2,Sub=x=>10*x}.Sub(1)          //结果10,注意优先调用Sub函数变量而不是列表本身的方法Sub

 

理论上方法写法也可以用函数代替(如Sub(xxx,2)),但方法比较直观,可以形成很有特色的链式写法,如A.B().C()….,一直点下去。

六、面向函数公式

6.1 函数调用

函数调用是S#公式使用最为广泛的表达方式,其基本格式是:函数名称(参数,…)。其中每个参数又可以是一个子公式,从而可以形成更加复杂的公式嵌套结构。

最常用的函数是数值函数,注意与其他语言不同,S#数值函数大都支持多种数据类型。例如求余弦函数值:

cos( 30 )                           //结果0.86602540378443871

cos( [ 10 , 20 , 30 ] )             //结果[0.984807753012208,0.93969262078590843,0.86602540378443871]

cos( { 10 , [ 20 , 30 ] , 40 } )    //结果{0.984807753012208,[0.93969262078590843,0.86602540378443871],0.766044443118978}

 

注意函数调用也会优先查找当前最近的局部变量堆栈,如果找不到就会到上一级的变量堆栈,一层层往上找,如果还找不到,就会去调用系统预定义函数(不同数据类型预定义了不同的函数,具体可以在软件"所有"面板中查阅),如果还没有则报错。例如:

{a=1, b=cos(30)}             //结果{a=1,b=0.86602540378443871}

{cos=x=>10*x, b=cos(30)}     //结果{cos=x=>10*x,b=300},注意优先调用cos函数变量而不是求余弦函数值。

 

6.2 函数定义

函数式语言强调的“函数是一等公民”,指的是函数自身也可以作为一个变量,即用户可自定义命名的函数变量,也支持先定义后引用,在不同上下文中可以定义同名函数变量。

上面的x=>10*x其实就是一种匿名的函数定义(与C#类似),由于函数定义有多种形式和高级使用特性,下面一个章节“一等公民函数爱巧”会专门讲函数定义。

六、系统专用公式

前面讲的都是其他语言大都有公式表达结构,本节列出的很多是S#特有的表达方式,语法上类似函数调用,但是使用专用关键字和分隔符。这里很多公式其实都等价于其他语言的语句功能了。

6.1 直接求值公式

parse/include/call(参数)

parse('(20+'+'30)/2')        //?算符的增强版,可对变量公式进行字符串解析并求值,结果25

include('c:\hjx. '+'shone')     //$算符的增强版,对变量公式进行字符串解析并打开文件,再对其内容解析并求值

call('c'+'os', 30)           //可以通过字符串变量公式,动态调用变量或函数,结果0.5

 

6.2 顺序求值公式

eval(局部变量堆栈: 结果公式)

顺序求值公式通过建立局部变量堆栈并对结果公式进行求值和输出。其中局部变量堆栈有一到多个变量赋值构成,用逗号分割。变量赋值写法是:变量名称=变量公式。结果公式可以直接引用局部变量,若输出数组可以采用省略写法。例如:

eval(a=1, b=2: a+b)        //结果3

eval(a=1, b=2: a,3$b,a)    //结果[1,2,2,2,1]

eval(a=1, b=2: {a,3$b,a})    //结果{1,[2,2,2],1}

 

顺序求值公式的使用非常广泛,能力等价于C#中的顺序求值语句{;;return;}。

6.3 条件求值公式

if(条件公式? 结果公式1 : 结果公式2)

条件求值公式先计算条件公式并进行判断,如果为真则对结果公式1进行求值和输出,否则对结果公式2进行求值和输出。条件公式没有变量堆栈,结果公式若输出数组也可以采用省略写法。例如:

if(10>5? 1: 2)        //结果1

if(10>5? 1,2: 2)      //结果[1,2]

if(10>5? 1;2: 2)      //结果{1,2}

 

条件求值公式的使用也非常广泛,能力等价于C#中的条件求值公式(?:)或语句(if else)。

6.4 分支求值公式

case(数据公式; 分支公式系列 : 缺省结果公式)

分支求值公式先计算数据公式,然后对分支公式系列的每个分支进行测试,如果等于某个分支公式值,则输出该分支结果公式的计算结果,否则输出缺省结果。每个分支的写法是:分支公式->结果公式,多个分支间用逗号分隔。分支公式没有变量堆栈,结果公式若输出数组也可以采用省略写法。例如:

case(1+2; 1->5, 3->10: 0)       //结果10

 

分支求值公式能力等价于C#中的分支求值语句(switch)。

6.5 判断求值公式

switch(单个变量赋值; 分支公式系列 : 缺省结果公式)

判断求值公式先计算变量公式并赋值,然后对分支公式系列的每个分支进行测试,如果某个分支公式值为真,则输出该分支结果公式的计算结果,否则输出缺省结果。与case差别是,switch有变量堆栈,分支公式必须是布尔值且最好引用前面赋值的变量,结果公式若输出数组也可以采用省略写法。例如:

switch(x=1+2; x<1->5, x>2->10: 0)          //结果10

 

判断求值公式能力等价于其他语言的模式匹配求值语句。

6.6循环求值公式

each/eachx(循环变量配对序列 : 结果元素公式)

each单重循环求值公式首先建立循环变量堆栈,并把循环变量配对的数据值(通常是数组或列表)逐个循环赋值给变量并对结果元素公式进行求值,最终合并输出为数组或列表。其中循环变量配对写法是:变量名称@数据公式。有多个循环变量配对时用逗号分割,注意每组循环次数以第一个变量为准。循环求值公式有局部变量堆栈,结果公式可以直接引用循环变量,若输出数组可以采用省略写法。另外each表示输出数组,eachx则输出列表。例如:

each(x@[1,2,3]: 2*x)   //结果[2,4,6]

each(x@1..5: 2*x)      //结果[2,4,6,8,10]

each(x@1..5: x,2*x)    //结果[1,2,2,4,3,6,4,8,5,10]

each(x@1..5, y@5..20: (2*x,y)) //多循环结果[(2,5),(4,6),(6,7),(8,8),(10,9)]

eachx(x@[1,2,3]: 2*x)          //结果{2,4,6}

eachx(x@1..5: 2*x)             //结果{2,4,6,8,10}

eachx(x@1..5: x,2*x)    //结果{[1,2],[2,4],[3,6],[4,8],[5,10]}

eachx(x@1..5: {x,2*x})   //结果{{1,2},{2,4},{3,6},{4,8},{5,10}}

eachx(x@1..5, y@5..20: (2*x,y))   //多循环结果{(2,5),(4,6),(6,7),(8,8),(10,9)}

 

each循环求值公式能力等价于C#语言的循环语句(foreach),甚至更强。

each/eachx(循环变量配对序列; 过滤条件公式 : 结果元素公式)

each单重循环求值公式中如果中间加入过滤条件,那么只输出符合过滤条件结果。例如:

each(k@1..5;k%2==0: k)        //结果[2,4]

 

each/eachx(索引变量: 循环变量配对序列 : 结果元素公式)

each单重循环求值公式中如果前面加入索引变量,那么在每次循环时会自动为索引变量赋值,从0开始,每个循环自动加1。例如:

each(i: k@1..5: k*10+i)       //结果[10,21,32,43,54]

 

dup/dupx(循环变量配对序列 : 结果元素公式)

dup多重循环求值公式语法与each类似,区别是多组循环时会输出多重循环的结果,数据量更多。例如:

dup(x@1..5, y@5..20: (2*x,y))       //多重循环结果[(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),(2,14),(2,15),(2,16),(2,17),(2,18),(2,19),(2,20),…]

dupx(x@1..5, y@5..20: (2*x,y))      //多重循环结果{{(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),(2,14),(2,15),(2,16),(2,17),(2,18),(2,19),(2,20)},… }

 

for/forx(循环变量堆栈; 循环条件公式; 变量赋值序列 : 结果元素公式)

for循环求值公式首先建立循环变量堆栈,并执行循环直到不满足条件公式,每次有效循环先对结果元素公式求值再执行循环赋值序列,最终合并输出为数组或列表。其中变量赋值的写法是:变量名称=赋值公式,有多个变量赋值时用逗号分割。for循环求值公式有局部变量堆栈,结果公式可以直接引用循环变量,若输出数组可以采用省略写法。另外for表示输出数组,forx则输出列表。例如:

for(i=0; i<5; i++: i*2)        //结果[0,2,4,6,8]

for(i=0,j=2; i<5; i++,j+=2: (i*2,j))      //多个变量结果[(0,2),(2,4),(4,6),(6,8),(8,10)]

for(i=0; i<5; i++: for(j=2; j<5; j+=2: (i*2,j))) //多重循环结果[(0,2),(0,4),(2,2),(2,4),(4,2),(4,4),(6,2),(6,4),(8,2),(8,4)]

 

for循环求值公式能力等价于C#语言的循环语句(for)。

6.7 迭代求值公式

iter (结果变量赋值; 循环变量配对序列 : 结果赋值公式)

iter迭代求值公式首先建立循环变量堆栈,初始化结果变量赋值,并把循环变量配对的数据值(通常是数组或列表)逐个循环赋值给循环变量并对结果赋值公式进行求值,最终输出结果变量的最终值。迭代求值公式有局部变量堆栈,结果赋值公式必须引用结果变量。例如:

iter(s=0; k@1..5: s+=2*k)    //结果30

 

iter (结果变量赋值; 索引变量: 循环变量配对序列 : 结果赋值公式)

iter迭代求值公式中如果中间加入索引变量,那么在每次循环时会自动为索引变量赋值,从0开始,每个循环自动加1。

iter(s=0; i: k@1..5: s+=i)   //结果10

 

do(结果变量赋值; 循环变量堆栈; 循环条件公式; 变量赋值序列 : 结果赋值公式)

do迭代求值公式首先建立循环变量堆栈,并执行循环直到不满足条件公式,每次有效循环先对结果赋值公式求值再执行循环赋值序列,最终输出结果变量的最终值。do迭代求值公式有局部变量堆栈,结果赋值公式必须直接引用结果变量。例如:

do(s=0; i=0; i<5; i++: s+=i) //结果10

 

6.8 区间累计公式

区间累计公式其实可以使用迭代公式替换,只是写法复杂一些。考虑到他们在数值计算中经常使用,因此设置专用公式可以提高表达能力。

sum(自变量=开始公式,结束公式; 求和公式)

sum区间累计求和公式首先建立自变量堆栈,并按区间从开始到结束步长为1,逐个循环赋值给自变量并对求和公式进行求值,最终合并输出累加结果。sum公式有局部变量堆栈,结果元素公式可以引用自变量。例如:

sum(x=1,5: x)        //结果15

 

prod(自变量=开始公式,结束公式; 求积公式)

prod区间累计求积公式与sum求和类似,区别是对求积公式结果进行乘法累积。例如:

prod(x=1,5: x)        //结果120

 

integal(自变量=开始公式,结束公式; 积分公式)

prod区间累计积分公式与sum求和类似,区别是结果是针对区间的定积分。注意积分公式不用包含dx。例如:

integal(x=0,5: 1)        //结果5

integal(x=0,5: x)        //结果12.5

integal(x=0,5: x*x)      //结果41.6666716337204

  

已有计算机专家论证过,编程语言只要具备顺序、条件、循环三大控制语句,其算法表达能力是等价的。S#在公式级别就提供了相当于其他语言语句级别的算法能力,更不用说S#还有语句级别的表达。

看了本文,您是否同意“S#是最炫酷的公式表达”?!

 

声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp。

软件: S#语言编辑解析运行器(ShoneSharp.13.6.exe),运行环境.NET4.0,单EXE直接运行,绿色软件无副作用。网盘链接https://pan.baidu.com/s/1nv1hmJn

posted on 2017-12-09 17:58  ShoneSharp  阅读(741)  评论(1编辑  收藏  举报