load help; load help_dao_type_number; load help_dao_type_enum; load help_dao_type_string; load help_dao_type_array; load help_dao_type_list; load help_dao_type_map; load help_dao_type_tuple; @[name] dao.type @[name] @[title] 数据类型和类型标注 @[title] @[text] 道语言支持可选(混合)类型系统,允许声明带或者不带类型标注的变量。 当一个变量的声明不带类型标注时,它的类型一般可根据一些规则被自动地推导出来。 对于有类型标注或能被推导出类型的变量,编译时的静态类型检查将被执行, 否则类型检查将回推迟到运行时执行。 @[section]类型标注@[section] 道里面的类型标注都是放在变量名,参数名或成员域名后面, 并用冒号与名称分开: @[code] 名称: 类型 名称: 类型 = 值 routine MyFunction( 名称: string, index: int = 1 ) { ... } class Klass { var 名称: 类型 = 值 } @[code] 多个变量可被同时声名有同样的类型和初始值: @[code] 名称1, 名称2 : 类型 名称1, 名称2 : 类型 = 值 @[code] @[section]数据类型@[section] 道支持丰富的数据类型,以便方便的开发程序。 @[subsection]基本类型@[subsection] 下表中所列的是基本道语言基本数据类型: @[table] ##类型名 ##类型描述 ##none &&空值类型 ##bool &&布尔类型 ##int &&整数类型(64位) ##float &&双精度浮点数类型(64位) ##complex &&双精度复数类型 ##string &&字符串类型 ##enum &&枚举符号类型 @[table] 尽管@[code]enum@[code]是一个基本类型,它的类型名可变,并可包括它所含的符号名称。 例如: @[code] var name: string var state: enum = $on @[code] @[subsection]复合类型@[subsection] 下表所列为道语言里的关键复合类型: @[table] ##类型名 ##类型描述 ##array < 元素类型 > &&数组类型 ##tuple < 元素类型或名字加类型 > &&元组类型 ##list < 元素类型 > &&列表类型 ##map < 键类型, 值类型 > &&关联表类型 @[table] 这些模板类型可有类型参数,这些类型参数通常表示这些复合类型所能装的元素类型。 例如: @[code] array # 可装整数的数值数组; list # 可装字符串的列表; map # 可装键为字符串类型值为整型的关联表; tuple # 可装一个字符串和一个整数的元组; # 下面的这个元组类型可装带名字的元素: # 它的第一个元素是名为"name"字符串类型; # 它的第二个元素是名为"index"整数类型; tuple @[code] 下面是一些用这些类型做类型标注的例子: @[code] var vector: array = [ 1, 2, 3 ] var tuple1: tuple = ( "abc", 123 ) var tuple2: tuple = ( 'abc', 123 ) var names: list = { "Joe" } var table: map = { "abc" => 123 } @[code] @[subsection]占位符类型@[subsection] 占位符类型的类型名都以@[code]@@[code]前缀开头,表示待定义的类型。 在一个局部环境中(如单个复合类型和函数原型等),同名的占位符类型表示同一个类型。 它们通常被作为类参数用在类模板类型里,或这些类型的方法原型里。 这样这些类型和它们的方法都变得通用化,并可正对特定的类型参数特例化。 例如,列表类型就被声明为@[code]list<@T=any>@[code]。 这里@[code]any@[code]是列表的缺省类型参数。 这意味着无参数的@[code]list<>@[code]类型将等同于@[code]list@[code]类型。 列表的一个方法的原型如下: @[code] append( self: list<@T>, item: @T, ... : @T ) => list<@T> @[code] 在这个原型里,所有占位符类型@[green]@T@[green]都表示同一个类型。 这意味着对于类型@[green]list<@T>@[green](以@[green]@T@[green]为@[green]string@[green] 作为例子),这个方法只能将类型为@[green]@T@[green] (@[green]string@[green]) 的值附加到@[green]list<@T>@[green]类型的列表里。 这里是个更简单的例子: @[code] tuple<@T,@T> @[code] 这表示这个元组类型只能装两个同类型的值。 @[subsection]任意类型@[subsection] 特殊类型@[code]any@[code]可用来表示任意类型。 将一个变量声明为@[code]any@[code]类型将有效地将这个变量的静态类型检查禁止。 @[subsection]值类型@[subsection] 道里面的另一个特殊类型是值类型。这种类型除了定义普通类型的类型信息, 它还定义了一个特定的常数值。当一个值匹配到此类型时,不但要类型匹配, 而且还要值匹配。 前面提到的空值类型@[code]none@[code]既被定义为一个值类型, 一般只有空值@[code]none@[code]本身能匹配到这个类型。 不过为了方便起见,封装空指针的C类型也能匹配到这个空值类型。 空值类型可象其它类型一样在任意地方使用,但它最通常的用途是用在 多型(variant)类型里,表示这个类型的变量或参数可以是空值。 其它的值类型不能象普通类型那样用在任意地方, 它们主要是用作复合类型的类型参数。例如: @[code] tuple<1,2> @[code] 表示一个第一个参数只能装整数@[code]1@[code]和第二个参数只能装整数@[code]2@[code] 的元组类型。 值类型的这种用法主要是对于封装C++模板类型有用。 @[subsection]多型(Variant)类型@[subsection] 多型(variant)类型是一种表示多个可能类型的类型。 这种类型的声明是通过将多个类型用分隔符@[code]|@[code]连起来声明。 被标注为多型类型的变量可保存这个多型类型所表示的任意一种可能类型的值。 例如: @[code] var intstr : int|string = 123 intstr = 'abc' @[code] 在道里,只有被标注为空值类型@[code]none@[code]或未定义的占位符类型或任意类型 的变量能保存空值。但有时已知有些其它特定类型变量的值可能为空值, 为了让道的类型系统能处理这种情况而不报错,这些变量的类型必须被标注为 包含@[code]none@[code]的多型类型。 例如: @[code] routine FindWord( source: string, word: string ) => int|none { var pos = source.find( word ) if( pos < 0 ) return none return pos } var pos1 = FindWord( "dao language", "dao" ) # pos1 = 0 var pos2 = FindWord( "dao language", "lua" ) # pos2 = none @[code] 详情请参看@[node]dao.type.variant@[node]。 @[subsection]函数类型@[subsection] 函数类型由关键词@[code]routine@[code]和其它类型构成。 这种类型一般由函数和方法的声明自动生成。 例如: @[code] routine Test( name: string, value = 123 ) { return value * 10; } io.writeln( std.about(Test) ) # Output: Test{routineint>}[0x7f9ef1c50340] @[code] 输出里的@[code]routineint>@[code] 就是为@[code]Test@[code]函数自动生成的函数类型名。 这里: @[list] --@[code]name:string@[code] : 第一个参数的类型信息,表示第一个参数名为@[code]name@[code], 且是字符串@[code]string@[code]类型; --@[code]value=int@[code] : 第二个参数的类型信息,表示第二个参数名为@[code]value@[code], 且是整数@[code]int@[code]类型,并且带有一个缺省参数值; --@[code]=>int@[code] : 被推导出的返回参数类型; @[list] 对于代码块方法,它们的类型名有额外的部分。 例如,列表类型的代码块排序方法有如下原型: @[code] sort( self: list<@T>, part = 0 )[X: @T, Y: @T => int] => list<@T> @[code] 它的类型名为: @[code] routine,part=int=>list<@T>>[X:@T,Y:@T=>int] @[code] 这里@[code][X:@T,Y:@T=>int]@[code]相对于普通函数类型的额外部分。 它表示这个方法的代码块可以使用两个类型为@[code]@T@[code]的参数, 并且应该产生一个整数作为代码块的返回结果。 当然函数类型也可被显示声明。例如变量就可按以下方式被标注为一个函数类型: @[code] var myfunc: routineint> @[code] @[subsection]泛型类型@[subsection] 道语言里的如列表和关联表等标准复合(容器)类型被作为泛型支持。 这种泛型支持也能被用户定义类型使用。 不过这种泛型类型只能在C模块里以由C实现的用户类型定义。 这种用户定义的类型,其定义方式和使用方式于内置的标准泛型类型 基本一直。 这种泛型类型的一个典型例子就是未来值类型@[code]mt::future<@V>@[code]。 尽管@[code]future@[code]是一个内置的类型,但用户可以以同样的方式定义 其它泛型类型。 详情请参看@[node]daovm.interface.extending@[node]里关于定义用户类型的技术。 @[comment] This special type name represents undefined types. Function parameters that have neither type annotations nor default values are automatically assigned with this type. Some variables whose types cannot be successfully inferred are also assigned with this type. This type can be specialized to a more specific type when more type information is available, for example, at function call point (compile time or runtime), parameters passed to a function call can provide concrete types to specialize the function (namely, specializing parameter types, local variable types and virtual machine instructions). @[comment] @[subsection]类型别名@[subsection] 道里面用户可以按以下语法给每个可显示声明的类型定义别名: @[code] #TypeAliasing ::= 'type' Identifier '=' Type type alias = another_type @[code] For example, @[code] type StringList = list type Point3D = tuple @[code] @[text] @[name] dao.type.variant @[name] @[title] 多型(Variant)类型 @[title] @[text] 一个多型(Variant或Disjoint Union)类型可用来标注一个变量的多个可能类型。 多型类型的表示由多个类型名通过@[code]|@[code]组合起来。 一个多型类型的变量可保存其任意一种可能类型的值。 例如: @[code] var intstr : int|string = 123 intstr = 'abc' @[code] 值得注意的是多型类型通常不支持它的可能类型的运算操作。它必须作适当的转换 才能用它真实类型的运算操作。 要检查一个值的类型,并根据不同的类型作不同的操作,最简单的办法是使用 基于类型的@[code]switch-case@[code]: @[code] switch( intstr ) type { case int : io.writeln( intstr * 1000 ) case string : io.writeln( intstr + 'abcdefg' ) } @[code] 这里每个类型项的代码块里,变量@[code]intstr@[code]可直接按相应的类型使用 而不作显式的转换。 不过,当一个多型类型仅有两个类型组成,并且其中之一为空值@[code]none@[code]类型, 那么这个多型类型可直接作另一类型使用而不需显式转换。 @[code] routine Test( maybelist: list|none ) { if( maybelist == none ) return maybelist.append( 123 ) # 用作list,无显式转换; } @[code] 上面提到的类型switch有两个变体: @[code] switch( var name = expression ) type { ... } switch( invar name = expression ) type { ... } @[code] 这俩都将声明一个仅在switch块里可用的变量名。 不过后者声明的是一个定变量,可防止该变量在switch块被修改。 更多例子: @[code] intstring: list = {}; intstring.append( 123 ); intstring.append( 'abc' ); #intstring.append( {} ); # typing error io.writeln( intstring, intstring[0], intstring[1] ); @[code] @[code] interface HasSizeMethod { routine Size()=>int; } class AA { routine Size()=>int{ return 10 } } class BB { routine Size()=>int{ return 20 } } routine Test( object: AA|BB|HasSizeMethod ) { # casting to an interface will invoke automatic binding: var object2 = (HasSizeMethod) object; io.writeln( object2.Size() ) } io.writeln( std.about( Test ) ); Test( AA() ) Test( BB() ) routine Test2( data: int|float|string ) { switch( data ) type { case int : io.writeln( 'handling int' ); case float : io.writeln( 'handling float' ); case string : io.writeln( 'handling string' ); } } Test2( 1 ); Test2( 1.0F ); Test2( 'abc' ); @[code] @[code] class FakeImage { var image = [1,2,3,4;5,6,7,8;11,12,13,14;15,16,17,18]; # instead of writing operator methods with all the combinations # such as tuple, tuple, ... # one can use disjoint union to simplify this. operator[]( i: int, js: tuple )=>array { # one can simply return image[i,js], but the following is for demonstration purpose: var j1 = 0; var j2 = image.dim(1) - 1; if( js[0] != none ) j1 = js[0]; if( js[1] != none ) j2 = js[1]; return image[i,j1:j2]; } } var image = FakeImage(); io.writeln( image[1,1:] ); io.writeln( image[2,:1] ); @[code] @[code] routine Sum( alist : list<@T> ) => @T { # reflect.trace(); return alist.sum(); } var s = Sum( { 1, 2, 3 } ); #s += 'a'; # typing error io.writeln( s ); var s2 = Sum( { 'a', 'b', 'c' } ); io.writeln( s2 ); @[code] @[text]