ShoneSharp语言(S#)的设计和使用介绍
系列(11)—“类”披炫服靓妆化成“表”
作者:Shone
声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp。
摘要: 匿名类指类定义体(即代码块)本身,使得类也成为所谓的“一等公民”,类也可以像变量一样进行赋值定义、传递和使用。S#语言的数据类就是程序代码,数据表是该代码运行时对应的数据值,为此本文介绍了数据表、类定义和类实例化的各种使用方法,让你体验S#强大的数据和信息表达能力。
软件: S#语言编辑解析运行器13.6.31,运行环境.NET 4.0,绿色软件,单个EXE直接运行,无副作用。网盘下载链接为https://pan.baidu.com/s/1nv1hmJn
有了前述S#公式、函数以及语句的铺垫,终于进入到本系列的高潮部分介绍———S#类,为配合本文例子,请使用最新软件13.6.31版运行本文示例。
一、引言
世界很复杂,世界也很简单。站在不同角度,收获视图也不一样。前面第四章第二节已经清晰地展示S#符号解析运行视图,提到了:
S#语言开始处理的是数据,最终获得的也是数据,从而形成周而复始的闭环,最终用于实现“数据 → 信息 → 智慧”的完美进化。
S#语言的表达其实也是数据加工成信息的过程,即从没有意义的数据,变成有指定意义的数据(信息)。那么怎么表达呢?
S#第一定律:万物皆对象,对象皆引用。
S#把所有数据值都看成是一个.NET Object对象,可以表达任意数据类型的数据值,而且所有数据都是.NET引用,默认情况下后面的操作不应修改引用数据的内容,而会产生新的应用,保证数据表达或加工过程不产生副作用。
S#第二定律:程序就是树,代码即节点。
S#把程序代码本身也看成是一棵语法数据树,每个树节点对应不同的语法结构代码,可以包含运行过程生成的数据值。因此可以把树节点看成是其数据值的包装,而且树节点本身也是一种数据值,这样就会统一了程序与数据之间的关系,达到“程序即数据,数据即程序”的无缝融合。
S#第三定律:程序以类聚,数据以表分。
第二定律指出程序也是数据,而程序与数据的关系是动态映射的。在程序运行过程中程序的一个数据节点通常保持不变,但其包含的数据值却是根据上下文动态变化的,即不同实例的数据值。因此相同功能的程序代码通常写成一个数据类(简称类),对一个类进行实例化后得到的是一个数据表(简称表)。再次强调与其他语言不同,S#类是程序代码,S#表是该代码运行时对应的数据值,这就是所谓的“类”披炫服靓妆化成“表”。
S#第四定律:无名成列表,有名数据表。
用道德经的名言“无名天地之始,有名万物之母”来描述S#对数据的理解再贴合不过了。上帝创世时的世间万物还都是无名,但取名这事,还是归功于人类的聪明才智,为此人类创造了语言。同样S#语言表达一系列无名的数据集合时采用列表,而表达一系列有名的数据集合时采用的是数据表。可见命名是S#语言表达数据的关键方式,也是把数据加工成信息的必经之路。
上面就是S#语言的圣经,如果您对编程语言领域(PL,Programming Language,网上引起很多争议的王垠就是这方面的专家)有比较深入的研究,本人上述的宏观论断或许会引发您的深思或顿悟。不过不懂没关系,懵懂也很好,下面我就会逐步讲解。
二、数据表
一个无名的数据值是这样子(数值):
10
一系列无名的同类数据值是这样子(数组):
[10, 20, 30]
一系列无名的各类数据值是这样子(列表):
{'box', 10, 20, 30, true}
一系列有名的各类数据值是这样子(数据表):
(1)标准写法
{Name='box', L=, W=, H=, Visible=true}
这种表达让我们看到了盒子及其长宽高等更多的数据含义,促使原始无意义的数据加工成了有意义的信息。这就是数据表的标准写法,可用于表达包含一系列键值数据对的集合,注意与其他语言不同,数据表带有局部变量堆栈,其键就是堆栈中的变量名称,而值就是变量的数据值。
如果每个键值都是数组,那么是不是与数据库的列对应,数据表也就相当于一张数据库中的表。关系数据库的理论已经证明表的关系演算与Lambada演算的表达能力是等价的,可见数据表有多么重要了。
{ Name=['box1', 'box2', 'box3'] , L=[, , ] , W=[, ] , H=[, , ] , Visible=[true, false, true] }
正因为数据表是如此地重要,S#语言支持很多种数据表的扩展写法:
(2)JSON写法
熟悉Javascript的同学有福了,把数据表标准写法的键名写成字符串,中间符号换成冒号,就成为JSON写法。例如:
{"Name":'box', "L":, "W":, "H":, "Visible":true}
现在JSON数据在网络上大行其道,而S#语言可以说就是JSON的超集,可以直接读取JSON,处理JSON数据简直太简单、方便和残暴了。
(3)脚本写法
熟悉脚本文件的同学有福了,把数据表标准写法去掉逗号,花括号换成括号即可简化成脚本写法。例如:
(Name='box' L= W= H= Visible=true)
这种写法没有,分隔符,由于S#把换行也看成空白,因此可以很容易直接导入外部常见的各种脚本或配置文本文件,就是加上()直接解析运行即可。这种脚本数据应用也很广泛,S#语言处理起来也非常简单、方便和残暴。
(4)YAML写法
熟悉YAML和Python的同学有福了,把数据表标准写法去掉逗号和花括号,中间符号换成冒号,加上===可扩展成YAML写法,例如:
=== Name: 'box' Size: L: W: H: Visible: true === //等价于标准写法 { Name = 'box' , Size = { L = , W = , H = } , Visible = True }
这种写法没有,分隔符,换行缩进自动处理成不同层级的嵌套,可以省略掉好多逗号和花括号,因此表达层级数据非常整齐和简洁,这点与Python处理数据有点异曲同工之妙。S#语言处理YAML也比较简单、方便。
除此之外数据表还有很多从类实例化的写法,本文后面也会专门介绍。简单的数据表也可以看作数据库中表的一条记录,如果有很多条类似这样的记录如下:
{Name='box1', L=10, W=20, H=30, Visible=true}
{Name='box2', L=11, W=21, H=31, Visible=false}
那么说明他们都是同一类型的数据,也就是有共同的模板。这时我们可以使用数据类对他们所属的分类或模板进行抽象表达——数据类(简称类),而实际的数据记录则可以按照该类进行实例化修改成对应的数据表。无名的数据类就是匿名类,有名的数据类通常通过赋值定义或显示定义。
三、匿名类
匿名类(也叫表模板、元表或自定义数据类)指数据类定义体(即代码块)本身,可以当作变量进行传递和使用,使得类成为所谓的“一等公民”。实现方式上也是通过指向类入口的指针、地址或引用传递,在程序后面调用匿名类时则把当前程序跳转到匿名类体上执行,当然每次执行还要处理同名称变量数据值的传递和堆栈进出。
(1)匿名类公式
class 缺省数据表
数据表前加class关键字就可以定义一个匿名类,可用于变量赋值。例如:
class {Name='box', L=, W=, H=, Visible=true}
(2)继承匿名类公式
class : 基类名称 缺省数据表
如果类是从指定的基类派生而来,那么class关键字后面指定一个基类名称即可,实例化的时候是先实例化基类数据再实例化当前类的数据。例如:
class : A {Name='box', L=, W=, H=, Visible=true}
S#类通常是有名字的,上述匿名类的表达方式比较简单便捷,通常用于公式中使用,复杂的请使用下面的类定义。
四、类定义
由于类是一等公民了,因此类通常也可以像变量一样进行赋值定义。
S#有两种类定义形式,但是其类实例化方法是一样的。
(1)隐式类定义
变量名称=匿名类
这种写法最为灵活多变,可使用在任何公式或语句中。因为匿名类被看作普通数据,可以通过命名变量进行传递和使用,无论是传给其他函数作为实参调用,或是作为其他函数的返回值,都没有任何违和感觉。例如:
{ f = class {a=, b=} , g = f{a=} , //直接调用,g结果{a=15,b=20} h = { a = f , b = a{b=} } //变量传递后间接调用,b结果{a=10 b=30} }
隐式类定义可以使用在任何变量赋值公式中,而且类变量的作用域还可以用专门语法进行修改。
(2)显式类定义语句(注:只能使用在语句中)
class 类名称 缺省数据表;
class关键字显示定义一个类名,进行类变量赋值。例如:
class Test {Name='box', L=, W=, H=, Visible=true}
class 类名称 语句块
与上面类似,只不过类定义体是语句块,支持更复杂代码逻辑,这时和C#的类定义代码很像。例如:
class Test { var L=; var W=; func GetArea() { Return L*W; } }
class 类名称 : 基类名称 缺省数据表;
class关键字显示定义一个类名,进行派生类变量赋值。例如:
class Test : A {Name='box', L=, W=, H=, Visible=true}
class 类名称 : 基类名称 语句块
同样类定义体也改成语句块,可支持更复杂代码逻辑,这时和C#的派生类定义代码很像。例如:
class Test : A { var L=; var W=; func GetArea() { Return L*W; } }
五、类实例化
一个类其实就是一段代码,只要进行实例化才能转化成为数据表进行使用,这与函数必须进行调用后才能转化为数据值进行使用相类似。
假设前面上下文已定义了类型A如下:
class A { L=10, W=20, Area=L*W }
那么可以使用以下四种S#的类实例化方式,可以用于任何公式和语句中。
(1)无参数的类实例化公式
类名称 {}
直接获得类代码的运行结果即数据表,注意类实例化后时还会把类名添加到数据表中。例如:
{ Rect = class {L = , W = , Area = L * W} , b = Rect{} // b结果为 { Class = 'Rect', L = 10 , W = 20 , Area = 200 } }
(2)带参数的类实例化公式
类名称 { 参数赋值系列 }
传入参数数据值并赋值给类代码中的同名变量,再运行类代码获得的结果数据表。注意如果传入参数没有找到同名变量,那么也会添加到结果数据表中,注意类实例化后时还会把类名添加到数据表中。例如:
{ Rect = class {L = , W = , Area = L * W} , b = Rect{L=, X=} // b结果为 { Class = 'Rect', L = 4 , W = 20 , Area = 80, X=50 } }
(3)带子节点的类实例化公式
类名称 { 类实例化公式系列 }
运行类代码并把子节点作为Children变量附加到的结果数据表,注意类实例化后时还会把类名添加到数据表中。例如:
{ Shape = class {Name = ""} , //定义基类 Line = class : Shape {L = } , //定义线类 Rect = class : Shape {L = , W = , Area = L * W} , //定义矩形类 b = Shape //实例化Shape类(带子节点) { Line{ Name = "l", L = } Rect{ Name = "r", L = } } } //b的计算结果为: { Class = 'Shape', Name = '' , Children = { { Class = 'Line', Name = 'l' , L = } , { Class = 'Rect', Name = 'r' , L = , W = , Area = } } }
(4)带参数及子节点的类实例化公式
类名称 { 参数赋值系列 } { 类实例化公式系列 }
传入参数数据值给类代码中的同名变量,再运行类代码并把子节点作为Children变量附加到的结果数据表。注意如果传入参数没有找到同名变量,那么也会添加到结果数据表中,另外类实例化后时还会把类名添加到数据表中。例如:
{ Shape = class {Name = ""} , //定义基类 Line = class : Shape {L = } , //定义线类 Rect = class : Shape {L = , W = , Area = L * W} , //定义矩形类 b = Shape{ Name="l" Extend="扩展数据"} //实例化Shape类(带参数和子节点) { Line{ Name = "l", L = } Rect{ Name = "r", L = } } } //b的计算结果为: { Class = 'Shape' , Name = 'l' , Extend = '扩展数据' , Children = { { Class = 'Line' , Name = 'l' , L = } , { Class = 'Rect' , Name = 'r' , L = , W = , Area = } } }
六、XML类实例化
S#还支持类似于XML的类实例化方式,与标准方式一一对应且功能等价,也可以用于任何公式和语句中。
(1)无参数的XML类实例化公式
<类名称/>
与XML相同,例如:
<Rect/>
(2)带参数的XML类实例化公式
<类名称 参数赋值系列/>
与XML相同,赋值序列中间用空白而不是逗号分隔,例如:
<Rect L=4 X=50/>
(3)带子节点的XML类实例化公式
与XML相似,但子节点的写法不同,这样便于扩展,例如:
<类名称/> { 类实例化公式系列 }
<Shape/> { <Line Name = "l" L = 5 /> <Rect Name = "r" L = 5 /> }
(4)带参数及子节点的XML类实例化公式
<类名称 参数赋值系列 /> { 类实例化公式系列 }
与XML相似,但子节点的写法不同,这样便于扩展,例如:
<Shape Name="l" Extend="扩展数据"/> { <Line Name = "l" L = 5 /> <Rect Name = "r" L = 5 /> }
七、类定义及实例化综合示例
下面给出一个有继承关系的类的语句级别,其实与C#类似,只不过S#更加关注于数据表达层面,不用进行序列化和反序列就能把数据全部计算并表达出来,用于动态扩展各类数据和信息是非常方便的。
{ class Drawing { var Layer = ''; var Color = 'ByLayer'; var LineType = 'ByLayer'; var LineWidth = 'ByLayer'; var X = ; var Y = ; } class Block:Drawing { var Rotation = ; var Scale = ; var Children = []; } class Line:Drawing { var Length = ; var Angle = ; } class Circle:Drawing { var Radius = ; } class Rectangle:Drawing { var Width = ; var Height = ; } return Drawing { Name = "图形", Author = "hjx" } { Line { Length = } Circle { Radius = } Block { Rotation = } { Line { Angle = } Circle { Radius = } } }; }
计算结果如下:
{ Class = 'Drawing' , Layer = '' , Color = 'ByLayer' , LineType = 'ByLayer' , LineWidth = 'ByLayer' , X = , Y = , Name = '图形' , Author = 'hjx' , Children = { { Class = 'Line' , Layer = '' , Color = 'ByLayer' , LineType = 'ByLayer' , LineWidth = 'ByLayer' , X = , Y = , Length = , Angle = } , { Class = 'Circle' , Layer = '' , Color = 'ByLayer' , LineType = 'ByLayer' , LineWidth = 'ByLayer' , X = , Y = , Radius = } , { Class = 'Block' , Layer = '' , Color = 'ByLayer' , LineType = 'ByLayer' , LineWidth = 'ByLayer' , X = , Y = , Rotation = , Scale = , Children = { { Class = 'Line' , Layer = '' , Color = 'ByLayer' , LineType = 'ByLayer' , LineWidth = 'ByLayer' , X = , Y = , Length = , Angle = } , { Class = 'Circle' , Layer = '' , Color = 'ByLayer' , LineType = 'ByLayer' , LineWidth = 'ByLayer' , X = , Y = , Radius = } } } } }
八、S#语言小结
至今为止,本系列博文把S#的基本语言特性大都介绍过了,还有一些高级特性后续博文继续讲解。不知您对S#语言有何看法?如有请把您理解的优势利弊发表在下面评论中。
总而言之,S#语言是一种基于.NET平台的面向表达的动态语言,他尽量兼收并蓄了其他语言常用的语法特性,把数据值、数组、列表、数据表、函数以及数据类全部都看做是一等公民,并通过对公式和语句两种语法基础结构的对全面支持和扩展,使得该语言化繁为简,表达能力相当出色。
声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp。
软件: S#语言编辑解析运行器13.6.31,运行环境.NET 4.0,绿色软件,单个EXE直接运行,无副作用。网盘下载链接为https://pan.baidu.com/s/1nv1hmJn