首页 > 代码库 > 脚本语言:Xmas(二)
脚本语言:Xmas(二)
本篇,来谈谈类型系统,以及部分与垃圾收集器相关的内容。
一、基本类型
Xmas的基本类型:Null、Boolean、Label、String、Ref、Function、Integer、Float、Decimal、Array、List、Set、Map、Object;14个,相对于其他的脚本语言是有些多的;但,这些类型都是“原子的”,少了其中一种,多少会有些不方便(但并非不完备,如Decimal可以代替所有算数类型;Map则可以可以替代所有容器类型)。如果,你看过上一篇,会发现有那么一点点的不同——一星期前,Xmas只有13种基本类型。
是的,【Label】是昨晚刚加入的(嗯,但经过了一段时间的思虑);其在Xmas代表了:一种编译期常量,或者说是可命名字符串常量的整数映射。加入的原因有二:
1、Object和Struct函数,缺少一种合适的参数类型:Object{ field : value} -> Object("field", value),这是之前的做法(当然,这个转换由编译器完成);但是,当我们想要纯手写时,每个字段的名字都使用字符串来代替,是一种不自然的方式;而非更自然的:Object(field, value)。这里【field】便是一个Label。
2、为了性能,有了Label,我们可以在运行时,大量使用Label来代替字符串;这将会有一个可观的回报(其实,也不会太大;部分字符串,是在编译器创建的,并且是共享的)。
唯一需要纠结的是:如何创建/申明一个Label?
其他语言中典型的有 :field(Ruby)、‘field(Lisp);很丑,说真的。本来Ruby的“:”还可以接受,但Xmas中的“:”已经有了几种用途了,再加入一种会给编译器带来一定的负担(有歧义的语法,处理需要费一些脑细胞)。
然后,想到了Xmas中的“统一的类型创建”:new Type() / new Type{}。后者是统一初始化的语法(比如Map可以:new Map{ val1 : 12};而不可以:new Map(val : 12));那么,Label也是需要支持这种形式的创建,那么其便可以这样:
var = new field;//创建一个Label(“field”),没有()/{}
于是便有了下面的 Object创建方式:
var0 = new Object{ field : value}; //统一初始化 var1 = new Object(new field, value); //手动函数调用
这比使用字符串的方式干净一些;当然,也有妥协,毕竟Label本身和其他的字面量(变量、参数、函数名)形式上没有任何的区别;而,我们却必须要去区别(否则,必须要使用类似统一初始化的附属结构来区分)
需要注意的是,new本身是带有在创建对象的意思,在Xmas中也没有例外;但Label是个例外,其是在编译期创建的,所以并没有任何额外的消耗。
二、创建
Xmas中每种类型都有至少3种创建的方式(除了前面的Label,其无法无歧义地申明):字面量、new Type()、new Type{}、Type()。
基本类型一般便有4中(至少形式上):
var0 = 12; var1 = new Integer(12); var2 = new Integer{12}; var3 = Integer(12);
其中让人困惑的是:new 是否有必要? 有:
1、只有加上new,才能够使用统一的初始化方式(这对于Map和Object很重要的方式)。
2、只有加上new,编译器才能够知道:你是在创建一个对象;才能够执行相应的额外处理(后面会讲)。
3、让你自己和别人知道,这句代码是在创建一个对象。
至于为什么不让编译器自己去识别对象的创建(如:var = Integer(12));可惜的是这是可不能的:
Integer = function(val){ return val + 1;}; var = Integer(12);
Xmas中对象的创建函数并不是关键字(也不可能,如自定义类);所以,上面的这段代码,编译器并不知道“Integer(12)”是创建一个整数,还是调用一个函数——实际上,正确的是函数调用。这就是,支持函数式编程的代价之一:函数调用的静态性没有了,因为任何变量都可能是一个函数。
所以,new便有了存在的必要:其强制声明一个对象的创建,其后面的是该类型的名字。当然,有了这个信息后(确定是否是对象创建),编译器便有了更多可以做的事情:自定义类型的创建优化。
本质上,一个自定义类型的创建需要,下面完整的四个步骤:
//1、创建一个空对象(类似于C++的分配内存) obj = new Object(); //这是一个函数调用,可以不加“new” //2、设置类信息(等价于C++的设置虚函数表) obj.class = new Class("AClass"); //这也是一个函数调用,并不需要“new” //3、调用自定义类的构造函数 obj.__init(); //这个字段是新加的,其保存了构造函数 //4、设置对象的final属性 try_final(obj); //这个比较复杂,后面会讲(涉及到GC)
这也是一个使用Reflection(反射)的完整案例;当然,这是手动的方式,也并非推荐的方法。有了new作为声明后,编译器可以将所有的这些操作,直接映射到一个系统函数的调用上:
obj = class("AClass"); //使用了和关键字“class”同名的函数
因为和关键字同名,所以代码中是不可以直接调用的;当然,你可以这样:
func = new Function("class"); //创建一个函数,通过函数名 obj = func("AClass");
当然,这不是重点;重点在于,如果不使用new来告诉编译器“这是在创建对象”;而是用:
obj = AClass(); //直接调用类的创建函数(由编译器生成的)
当然,如果前面没有一个名为“AClass”的变量的情形;编译器是可以推断出这是一次对象创建的,只是我懒;而且这并不是推荐的做法。更关键的是,为了识别出“这是一次对象创建”,编译器需要知道额外的信息:知道有这么一个类型,名叫“AClass”。而非,可以推迟一些时间。
三、面向对象
我们都知道,本质上面向对象,只是对象的内存布局如何设置(父类和子类),以及虚函数表如何实现。
在前面,我们已经知道了Xmas中的虚函数表:一个名为“class”的字段——其中放置了自定义类型的所有信息(成员函数,类型信息)。那么,【.class】中究竟有什么?
class AClass :Xmas{ function AClass(){ this.Xmas(); //调用父类的构造函数 } function func(){} }
上面的定义,究竟会生成怎样的class?
class: ....父类Xmas的东西.... __init = Function : AClass.AClass //AClass的构造函数 __type = Function : AClass //编译器生成的创建函数(其内部调用:class("AClass");) __name = String : “AClass” //类型名字 __final = Boolean : true/false //类型的final属性 AClass = Function : AClass func = Function : AClass.func //自定义的函数
很多东西是显而易见的:新加的__init是为使用反射创建对象时的方便,否则就需要如下的方式:
....... obj.constructor = get_value(obj.class, "AClass"); //批量反射创建时,我们可能只知道字符串形式的类型名称 obj.constructor(); ....... // 以上对应了: obj.AClass();
关于虚函数表这个东西,只要知道有这么一个东西;便也能够很自然构造出对应的。但另一个问题:对象的内存布局,一直是面向对象的一个大问题。除了C++其他语言使用interface来解决这个。是的,只要禁止了多重继承,这个问题便变得很简单。
对此Xmas,并没有给出合适的方案:简单的成员覆盖——父类的重名字段和函数,都只是简单地覆盖了,不可访问;即使在父类的空间也不可能。原因,很简单我个人暂时不会用到面向对象的那些功能;所以,也不会耗费精力去做.......懒癌,晚期。
当然,Xmas不止这些;通过反射,我们可以很轻易地构造出一个对象;但,或许我们可以这样:
obj = new Object(); obj.class = 12; //这算是恶作剧?
Xmas并不会让这段代码得以运行;在执行到“obj.class = 12;”时,会检测【右值】是否真的是一个class。这意味着,我们不能够通过反射,手动地创建出一个新的类型(在几天前,还是可以的)。Xmas限制了反射的能力——变成“只读”;但同时,也禁止了创建出无效对象的可能——“class”字段是一个内部字段,只能够用来放置类型信息。
需要额外一提的是,Xmas的类型信息是按需生成的——在第一次创建该自定义类型时,才构建对应的类型信息。
四、final属性
在Xmas中,一个对象有三重意义上的【不可变】:
1、除了容器类型(Array、List、Set、Map、Object),其他类型都是【不可变的】。
2、容器类型,拥有一个可设置的final属性;而且只可设置一次:将其设置为【不可变】(默认是可变的)。
3、自定义类型,在继承了第二点的同时(因为其使用Object作为容器);额外有一个由编译器生成的final属性(即:class.__final)。
对于第一点,很容易理解:类似Java中String,其是不可变的,对其任何的操作都将创建一个新的String。同样,Xmas中的不可变类型,也是如此——你没有任何办法改变一个Integer内部的值(这很有函数式风格)。这点是有两面性的:
1、不可变性,意味着任何的计算都将创建新的对象;而非复用,这无疑加重了GC的负担(最典型的是循环中的游标,循环1万次,需要创建1万个游标)。
2、不可变性,意味着对象复用没有任何的负担;一个String可以在整个代码中到处复用,而不用担心其被改变;相反,这减少了GC的负担(但减少的并不多)。
这对于编译器而言,其意义更大:编译时,可以放心地复用任何对象(编译期只会创建简单的不可变对象),而不用担心被污染。
而第二点,类似C++的const修饰:所修饰的对象不可变,但其成员中指针所指向的对象,并不能限制。就一门语言而言,这样的属性,并没有必要的存在(许多语言都没有);而且这个不可变属性,是绝对的——一旦设置了【不可变】,不可更改,该对象永远都不再可变!
list = new Array{12, ‘hh‘, 2.2}; final(list); //设置对象为不可变 list[1] = 32 //错误!运行时,会抛出异常
第三点,是继承了第二点的概念,但有所扩展:一个自定义对象创建后,其会被自动地设置final属性(根据class.__final中的值):
try_final(obj);
// 等价于
if(obj.__final){
final(obj);
}
其目的同样是,为了完成相同的目的:设置对象为【不可变】,自动地。
所以,唯一的疑问就是:class.__final从何而来?
class ClassA{ function ClassA(){ this.value = http://www.mamicode.com/12; } } class ClassB: ClassA{ function ClassB(){} function setValue(val){ this.value =http://www.mamicode.com/ val; } }
其中ClassA.class._final = true,而ClassB.class.__final = false。为什么? 就该字段的意义而言,其意味着:该类型的实例,是否允许被更改。Xmas,有这么一个简单的逻辑——如果类定义中,没有任何函数更改【this】的内容;那么,该类型便是不可变的。
所以,想要定义一个可变的类,是需要对应的【setXXX】系类函数的。 所以,ClassB有一个函数改变了this的内容,因此是可变的。当然,构造函数内部的任何操作,并不影响final属性——因为,构造函数只会被调用一次。
五、GC
为什么会有垃圾收集器出现在这里? 因为,【final】本身是为了GC存在的。
前面有讲过Xmas的GC是基于标记的分代式收集器;因此,为了完成真正意义上的分代:进行young GC时,并不会去标记老年代的对象(尽可能地)。Xmas提供final抽象:一个final对象其直接所持有的对象,其所处的分代并不会,从老年代降到年轻代,只会从年轻代升到老年代。
这句话很是抽象;其大致上代表了:
val = 12; obj = new Object{ val : val}; final(obj); .....gc...... //1、obj,val同时进入老年代 //2、obj为final,那么obj不可能有指向年轻代的引用(任何时刻) .....young gc..... //因为obj为老年代,且没有指向年轻代的引用,所以不必扫描
就结果而言;在有大规模的存活对象时(多数已晋升到老年代;因为年轻不可能有大量的对象);进行young GC时,其所需要扫描的对象比例在10:1~1000:1之间(Xmas得到的结果)。这样的效果是卓越的,GC总时间减少了80%-90%。
当然,这样的假设只有在:拥有大量不可变对象的背景下,才会成立。Xmas为此付出大量努力。比如对象的创建有两种方式:new Object{}、new Struct{}。区别在于,Struct创建的对象,其被设置了不可变。Xmas通过GC免去了内存管理的烦恼;但同样地也给予了一定的干涉GC的能力,当然,或者说是辅助GC的更好地工作(Xmas的自定义类的final属性,是自动化辅助GC的能力;并不需要手动干预,也不能)。
至于原因嘛?因为,GC并非Xmas的一个子模块,而是另一个完全独立的系统;其存在并非只是为了Xmas(大部分是),还有我的库中的其他模块。
PS:否则需要另外的技术来避免大量的老年代对象被扫描,而且其有着额外的代价。
六、类型系统
Xmas有一个获取对象类型的关键字函数:typeof。其返回的东西,很有意思:函数。在最早的版本中,返回的是字符串(当然,现在的话,也可以考虑Label)。
但返回函数,能够更优雅地解决问题。
obj = new ClassA(); if(typeof(obj) == ClassA){ print(‘he he‘); }
通过返回该类型的创建函数;我们可以写出更直白和方便的代码:不仅免除了字符串的引号,而且可以这样:
var = typeof(obj)("I‘m a param");
上面的代码创建了一个obj类型的新的实例,即使我们并不知道obj的类型是什么(函数作为第一值类,还有很有价值的)。
脚本语言:Xmas(二)