类型系统
Raku类型的定义
类型通过创建类型对象来定义新对象,该类型对象提供用于创建对象实例或检查值的接口。任何类型对象都是 Any 或 Mu 的子类。通过从这些基类和内省后缀 .^ 继承来提供内省方法。在编译时由以下类型声明符之一或在运行时使用元对象协议将新类型引入当前作用域。所有类型名称的作用域必须是唯一的。
默认类型
如果用户没有提供类型,则 Raku 假定类型为 Any
。这包括容器,基类,参数和返回类型。
my $a = 1;
$a = Nil;
say $a.^name;
# OUTPUT: «Any»
class C {};
say C.^parents(:all);
# OUTPUT: «((Any) (Mu))»
对于容器,默认类型为 Any
,但默认类型约束为 Mu
。请注意,绑定会替换容器,而不仅仅是值。在这种情况下,类型约束可能会变。
类型对象
要测试对象是否为类型对象,请对使用类型为 smiley 或 .DEFINITE
方法约束的类型使用 smartmatch:
my $a = Int;
say $a ~~ Mu:U;
# OUTPUT: «True»
say not $a.DEFINITE;
# OUTPUT: «True»
如果调用者是实例,则 .DEFINITE
将返回 True
。如果它返回 False
,则调用者是一个类型对象。
Undefinedness
未定义的对象在 Raku 中维护类型信息。类型对象用于表示未定义值和未定义值的类型。要提供一般的未定义值,请使用 Any。如果要区分容器和参数的默认类型 Any
,则需要使用 Mu
。
由 .CREATE 创建的对象实例是按惯例定义的。方法 .defined 将返回 Bool::True
以指示定义。该规则的例外是 Nil 和 Failure。请注意,任何对象都可以重载 .defined
,因此可以携带其他信息。此外,Raku 明确区分了定义和真假。很多值是有定义的, 即使它们具有错误或空值的含义。这些值为 0
,Bool::False, () (空列表) 和 NaN。
值可以在运行时通过 mixin 变为未定义。
my Int $i = 1 but role::{ method defined { False } };
say $i // "undefined";
# OUTPUT: «undefined»
要测试定义需调用 .defined
,请使用 //,with/without 和 signatures。
强制转换
将一种类型转换为另一种类型是使用与目标类型同名的强制方法完成的。Signatures 强制要求此约定。源类型必须知道如何将自身转换为目标类型。要允许内置类型将自己转换为用户定义的类型,请使用 augment 或者 MOP。
class C {
has $.int;
method this-is-c { put 'oi' x $!int ~ '‽' }
}
use MONKEY-TYPING;
augment class Int {
method C { C.new(:int(self))}
}
my $i = 10;
$i.=C;
$i.this-is-c();
# OUTPUT: «oioioioioioioioioioi‽»
Raku 提供了在 Cool 中定义的方法,以便在应用进一步操作之前转换为目标类型。大多数内置类型都来自 Cool
,因此可能会提供可能不需要的隐式强制。用户有责任关心这些方法的无陷阱使用。
my $whatever = "123.6";
say $whatever.round;
# OUTPUT: «124»
say <a b c d>.starts-with("ab");
# OUTPUT: «False»
类型声明符
类型声明符将新类型引入给定作用域。嵌套作用域可以用 ::
分隔。如果不存在此类作用域,则会自动创建新 packages。
class Foo::Bar::C {};
put Foo::Bar::.keys;
# OUTPUT: «C»
可以使用仅包含 ...
的块来提供前置声明。如果定义了类型,编译器将在当前作用域的末尾检查。
class C {...}
# many lines later
class C { has $.attr }
类
class
声明符创建一个编译为类型对象的编译时构造。后者是一个简单的 Raku 对象,它提供了通过执行初始化程序和子方法来构造实例的方法,以填充在类中声明的所有属性,以及任何具有值的父类。初始化程序可以提供属性声明或构造函数。 Metamodel::ClassHOW 负责知道如何运行它们。这是在 Raku 中构建对象的唯一神奇部分。默认父类型是 Any
,它继承自 Mu
。后者提供了默认的按照惯例命名的构造函数 .new
。除此之外,.new
不具有任何特殊含义,也不以任何特殊方式对待。
有关如何使用类的更多信息,请参阅类和对象教程。
Mixins
class
引入的类型可以在运行时使用 infix: 进行扩展。原始类型不会被修改,而是返回一个新的类型对象,并且可以存储在一个容器中,该容器对原始类型或混合的角色进行成功类型检查。
class A {}
role R { method m { say 'oi‽' } }
my R $A = A but R;
my $a1 = $A.new;
$a1.m;
say [$A ~~ R, $a1 ~~ R];
# OUTPUT: «oi‽[True True]»
自省
元类
要测试给定类型对象是否为类,请针对 Metamodel::ClassHOW 测试元对象方法 .HOW
。
class C {};
say C.HOW ~~ Metamodel::ClassHOW;
# OUTPUT: «True»
私有属性
私有属性用任何一个 $!
,@!
和 %!
twigils 来处理。它们没有自动生成的公共访问器方法。因此,它们不能从它们所定义的类的外面进行更改。
class C {
has $!priv;
submethod BUILD { $!priv = 42 }
};
say (.name, .package, .has_accessor) for C.new.^attributes;
# OUTPUT: «($!priv (C) False)»
方法
method
声明符定义 Method 类型的对象,并将其绑定到类的作用域中提供的名称上。默认情况下,类中的方法具有 has
作用域。our
作用域的那些方法默认不会添加到方法缓存中,因此不能使用访问器符号 $.
来调用。使用完全限定名称和调用者作为第一个参数来调用它们。
继承和 multis
子类中的普通方法不与父类中的 multis 竞争。
class A {
multi method m(Int $i){ say 'Int' }
multi method m(int $i){ say 'int' }
}
class B is A {
method m(Int $i){ say 'B::Int' }
}
my int $i;
B.new.m($i);
# OUTPUT: «B::Int»
Only 方法
要明确声明方法不是 multi 方法,请使用 only
方法声明符。
class C {
only method m {};
multi method m {};
};
# OUTPUT: «X::Comp::AdHoc: Cannot have a multi candidate for 'm' when an only method is also in the package 'C'»
Submethod BUILD
submethod BUILD
是(间接地)由被称为 .bless 的方法调用的。它旨在设置类的私有和公共属性,并接收传入 .bless
的所有名称属性。定义在 Mu
中的默认构造函数 .new 是调用它的方法。鉴于公共访问器方法在 BUILD
中不可用,您必须使用私有属性表示法。
class C {
has $.attr;
submethod BUILD (:$attr = 42) {
$!attr = $attr
};
multi method new($positional) {
self.bless(:attr($positional), |%_)
}
};
C.new.say; C.new('answer').say;
# OUTPUT: «C.new(attr => 42)
# C.new(attr => "answer")»
Fallback 方法
当其他解析名称的方法不产生结果时,将调用具有特殊名称的 FALLBACK
方法。第一个参数保存名称,所有后续参数都从原始调用转发。支持 multi 方法和子签名。
class Magic {
method FALLBACK ($name, |c(Int, Str)) {
put "$name called with parameters {c.perl}" }
};
Magic.new.simsalabim(42, "answer");
# OUTPUT: «simsalabim called with parameters ⌈\(42, "answer")⌋»
保留方法名
一些内置的内省方法实际上是由编译器提供的特殊语法, 即 WHAT
,WHO
,HOW
和 VAR
。使用这些名称声明的方法将无声地失败。动态调用将起作用,允许从外部对象调用方法。
class A {
method WHAT { "ain't gonna happen" }
};
say A.new.WHAT; # OUTPUT: «(A)»
say A.new."WHAT"() # OUTPUT: «ain't gonna happen»
包作用域中的方法
任何 our
作用域方法都将在类的包作用域内可见。
class C {
our method packaged {};
method loose {}
};
say C::.keys
# OUTPUT: «(&packaged)»
使用同名变量和方法设置属性
如果您用和属性属性同名的名字设置属性的变量(或方法调用),则可以节省一些输入,例如 attr => $attr
或 :attr($attr)
:
class A { has $.i = 42 };
class B {
has $.i = "answer";
method m() { A.new(:$.i) }
# ^^^^ Instead of i => $.i or :i($.i)
};
my $a = B.new.m;
say $a.i; # OUTPUT: «answer»
由于 $.i
方法调用名字叫 i
且属性也叫 i
,因此 Raku 允许我们使用快捷方式。这同样适用于 :$var
,:$!private-attribute
,:&attr-with-code-in-it
,等等。
trait is nodal
标记一个List方法,指示 hyperoperator 不要进入内部 Iterables 以调用此方法。这个特性通常不是终端用户会使用的东西,除非他们子类化或扩展核心 List 类型。
为了证明差异,请考虑以下示例,第一个使用 is nodal
方法(elems
),第二个使用方法 Int
, 它不是节点方法。
say ((1.0, "2", 3e0), [^4], '5')».elems; # OUTPUT: «(3, 4, 1)»
say ((1.0, "2", 3e0), [^4], '5')».Int # OUTPUT: «((1 2 3) [0 1 2 3] 5)»
trait handles
定义为:
multi sub trait_mod:<handles>(Attribute:D $target, $thunk)
trait handles
应用于类的属性,会将对提供的方法名称的所有调用代理给和属性同名名的方法。必须初始化属性引用的对象。可以提供代理调用的对象的类型约束。
class A { method m(){ 'A::m has been called.' } }
class B is A { method m(){ 'B::m has been called.' } }
class C {
has A $.delegate handles 'm';
method new($delegate){ self.bless(delegate => $delegate) }
};
say C.new(B.new).m(); # OUTPUT: «B::m has been called.»
可以提供一个 Pair
(或用于重命名)或一个 Pair
,Regex
或 Whatever
的列表而不是一个方法名。在后一种情况下,在类本身及其继承链中的现有方法将优先。如果 FALLBACK
要搜索本地,请使用 HyperWhatever
。
class A {
method m1(){}
method m2(){}
}
class C {
has $.delegate handles <m1 m2> = A.new()
}
C.new.m2;
class D {
has $.delegate handles /m\d/ = A.new()
}
D.new.m1;
class E {
has $.delegate handles (em1 => 'm1') = A.new()
}
E.new.em1;
trait is
定义为:
multi sub trait_mod:<is>(Mu:U $child, Mu:U $parent)
trait is
接受一个类型对象,该类型对象在其定义中被添加为类的父类。为了允许多重继承,可以多次应用 is
trait。将父类添加到类中会将其方法导入目标类。如果在多个父类中出现同名方法,则第一个添加的父类将胜出。
如果没有提供 is
trait,则默认值 Any
将用作父类。这迫使所有 Raku 对象具有相同的基本方法集,以提供内省和强制到基本类型的接口。
class A {
multi method from-a(){ 'A::from-a' }
}
say A.new.^parents(:all).perl;
# OUTPUT: «(Any, Mu)»
class B {
method from-b(){ 'B::from-b ' }
multi method from-a(){ 'B::from-A' }
}
class C is A is B {}
say C.new.from-a();
# OUTPUT: «A::from-a»
trait is rw
定义为:
sub trait_mod:<is>(Mu:U $type, :$rw!)
类的trait is rw
在该类的所有公共属性上创建可写的访问器方法。
class C is rw {
has $.a;
};
my $c = C.new.a = 42;
say $c; # OUTPUT: «42»
trait is required
定义为:
multi sub trait_mod:<is>(Attribute $attr, :$required!)
multi sub trait_mod:<is>(Parameter:D $param, :$required!)
将类或角色属性标记为必要的。如果在对象构造时未初始化该属性,则抛出 X::Attribute::Required。
class Correct {
has $.attr is required;
submethod BUILD (:$attr) { $!attr = $attr }
}
say Correct.new(attr => 42);
# OUTPUT: «Correct.new(attr => 42)»
class C {
has $.attr is required;
}
C.new;
CATCH { default { say .^name => .Str } }
# OUTPUT: «X::Attribute::Required => The attribute '$!attr' is required, but you did not provide a value for it.»
你可以为 is required
提供一个理由作为参数,说明它为什么是必须的。
class Correct {
has $.attr is required("it's so cool")
};
say Correct.new();
# OUTPUT: «The attribute '$!attr' is required because it's so cool,but you did not provide a value for it.»
trait hides
trait hides
提供继承而不需要重新分派。
class A {
method m { say 'i am hidden' }
}
class B hides A {
method m { nextsame }
method n { self.A::m }
};
B.new.m;
B.new.n;
# OUTPUT: «i am hidden»
trait is hidden
允许类从 重新分派 中隐藏自己。
class A is hidden {
method m { say 'i am hidden' }
}
class B is A {
method m { nextsame }
method n { self.A::m }
}
B.new.m;
B.new.n;
# OUTPUT: «i am hidden»
trait trusts
要允许一个类访问另一个类的私有方法,请使用该 trait trusts
。可能需要可信类的前置声明。
class B {...};
class A {
trusts B;
has $!foo;
method !foo { return-rw $!foo }
method perl { "A.new(foo => $!foo)" }
};
class B {
has A $.a .= new;
method change { $!a!A::foo = 42; self }
};
say B.new.change;
# OUTPUT: «B.new(a => A.new(foo => 42))»
扩展类
要在编译时向类添加方法和属性,请在类定义片段前面使用 augment
。编译器将要求编译指令 use MONKEY-TYPING
或 use MONKEY
早一点出现在同一作用域中。请注意,可能会对性能产生影响,因此可能会出现问题。
use MONKEY; augment class Str {
method mark(Any :$set){
state $mark //= $set; $mark
}
};
my $s = "42";
$s.mark(set => "answer");
say $s.mark
# OUTPUT: «answer»
在类片段内可以做什么的限制很少。其中之一是将方法或子方法重新声明为 multi
方法。使用添加的属性尚未被实现。请注意,添加仅在其命名参数方面不同的多候选项将在已定义的候选项后面添加该候选项,因此调度程序不会选择该候选项。
版本和作者
版权和作者身份可以通过副词 :ver<>
和 :auth<>
应用。两者都以字符串作为参数,对于 :ver
, 字符串被转换为 Version 对象。查询类版本和作者请使用 .^ver
和 ^.auth
。
class C:ver<4.2.3>:auth<me@here.local> {}
say [C.^ver, C.^auth];
# OUTPUT: «[v4.2.3 me@here.local]»
role
角色是类片段,它允许定义类共享的接口。role
声明符还引入了可用于类型检查的类型对象。角色可以在运行时和编译时混合到类和对象中。role
声明符返回创建的类型对象因而允许匿名角色和就地混入定义。
role Serialize {
method to-string { self.Str }
method to-number { self.Num }
}
class A does Serialize {}
class B does Serialize {}
my Serialize @list;
@list.push: A.new;
@list.push: B.new;
say @list».to-string;
# OUTPUT: «[A<57192848> B<57192880>]»
使用 ...
作为方法体的唯一元素声明一个要抽象的方法。任何混合使用这种方法的类都必须重载它。如果在编译单元结束之前该方法没有被重载,则抛出 X::Comp::AdHoc
。
EVAL 'role R { method overload-this(){...} }; class A does R {}; ';
CATCH { default { say .^name, ' ', .Str } }
# OUTPUT: «X::Comp::AdHoc Method 'overload-this' must be implemented by A because it is required by roles: R.»
自动双关
可以使用角色而不是类来创建对象。由于角色在运行时不能存在,因此会创建一个同名的类,该类将对角色类型检查成功。
role R { method m { say 'oi‽' } };
R.new.^mro.say;
# OUTPUT: «((R) (Any) (Mu))»
say R.new.^mro[0].HOW.^name;
# OUTPUT: «Raku::Metamodel::ClassHOW»
say R.new ~~ R;
# OUTPUT: «True»
trait does
trait does
可以应用于提供编译时混合的角色和类。要引用尚未定义的角色,请使用前置声明。混合角色的类的类型名称不反射 mixin,类型检查反射。如果在多个混合角色中提供方法,则首先定义的方法优先。可以提供以逗号分隔的角色列表。在这种情况下,将在编译时报告冲突。
role R2 {...};
role R1 does R2 {};
role R2 {};
class C does R1 {};
say [C ~~ R1, C ~~ R2];
# OUTPUT: «[True True]»
参数化
可以在角色名称后面的 []
之间提供角色的参数。支持类型捕获。
role R[$d] { has $.a = $d };
class C does R["default"] { };
my $c = C.new;
say $c;
# OUTPUT: «C.new(a => "default")»
参数可以有类型约束,类型不支持 where
子句,但可以通过 subset
实现。
class A {};
class B {};
subset A-or-B where * ~~ A|B;
role R[A-or-B ::T] {};
R[A.new].new;
可以提供默认参数。
role R[$p = fail("Please provide a parameter to role R")] {};
my $i = 1 does R;
CATCH { default { say .^name, ': ', .Str} }
# OUTPUT: «X::AdHoc: Could not instantiate role 'R':Please provide a parameter to role R»
As 类型约束
在期望类型的任何地方,角色都可以用作类型约束。如果使用 does
或 but
混合角色,则其 type-object 将添加到相关对象的 type-object 列表中。如果使用角色而不是类(使用自动生成),则自动生成的类与角色同名的类型对象将添加到继承链中。
role Unitish[$unit = fail('Please provide a SI unit quantifier as a parameter to the role Unitish')] {
has $.SI-unit-symbol = $unit;
method gist {
given self {
# ...
when * < 1 { return self * 1000 ~ 'm' ~ $.SI-unit-symbol }
when * < 1000 { return self ~ $.SI-unit-symbol }
when * < 1_000_000 { return self / 1_000 ~ 'k' ~ $.SI-unit-symbol }
# ...
}
}
}
role SI-second does Unitish[<s>] {}
role SI-meter does Unitish[<m>] {}
role SI-kilogram does Unitish[<g>] {}
sub postfix:<s>(Numeric $num) { ($num) does SI-second }
sub postfix:<m>(Numeric $num) { ($num) does SI-meter }
sub postfix:<g>(Numeric $num) { ($num) does SI-kilogram }
sub postfix:<kg>(Numeric $num){ ($num * 1000) does SI-kilogram }
constant g = 9.806_65;
role SI-Newton does Unitish[<N>] {}
multi sub N(SI-kilogram $kg, SI-meter $m, SI-second $s --> SI-Newton ){ ($kg * ($m / $s²)) does SI-Newton }
multi sub N(SI-kilogram $kg --> SI-Newton) { ($kg * g) does SI-Newton }
say [75kg, N(75kg)];
# OUTPUT: «[75kg 735.49875kN]»
say [(75kg).^name, N(75kg).^name];
# OUTPUT: «[Int+{SI-kilogram} Rat+{SI-Newton}]»
enum
枚举提供具有关联类型的常量键-值对。任何键都属于该类型,并作为符号注入当前作用域。如果使用该符号,则将其视为常量表达式,并将该符号替换为枚举对的值。任何枚举都从角色 Enumeration
继承方法。不支持用于生成键值对的复杂表达式。通常,enum
是一个 Map 其元素具有混合的Enumeration
角色; 对于每个元素,此角色包括在 map 上创建顺序的索引。
符号的字符串化,在字符串上下文中自动完成,并且与其名称完全相同,这也是枚举对的键。
enum Names ( name1 => 1, name2 => 2 );
say name1, ' ', name2; # OUTPUT: «name1 name2»
say name1.value, ' ', name2.value; # OUTPUT: «1 2»
比较符号将使用类型信息和枚举对的值。支持 Num
类型和 Str
类型。
enum Names ( name1 => 1, name2 => 2 );
sub same(Names $a, Names $b){
$a eqv $b
}
say same(name1, name1); # OUTPUT: «True»
say same(name1, name2); # OUTPUT: «False»
my $a = name1;
say $a ~~ Names; # OUTPUT: «True»
say $a.^name; # OUTPUT: «Names»
所有键必须属于同一类型。
enum Mass ( mg => 1/1000, g => 1/1, kg => 1000/1 );
say Mass.enums;
# OUTPUT: «Map.new((g => 1, kg => 1000, mg => 0.001))»
如果没有给出值,则 Int
将假定为值类型,并且每个键从零开始递增 1。作为枚举键类型 Int
,Num
,Rat
和 Str
都被支持。
enum Numbers <one two three four>;
say Numbers.enums;
# OUTPUT: «Map.new((four => 3, one => 0, three => 2, two => 1))»
可以提供不同的起始值。
enum Numbers «:one(1) two three four»;
say Numbers.enums;
# OUTPUT: «Map.new((four => 4, one => 1, three => 3, two => 2))»
枚举也可以是匿名的,和具名 enum
的唯一的区别在于您不能在签名中使用它或用它声明变量。
my $e = enum <one two three>;
say two; # OUTPUT: «two»
say one.^name; # OUTPUT: «»
say $e.^name; # OUTPUT: «Map»
有多种方法可以访问已定义的符号的键和值。所有这些都将值转换为 Str
,这可能是不可取的。通过将枚举视为包,我们可以获得键的类型列表。
enum E(<one two>);
my @keys = E::.values;
say @keys.map: *.enums;
# OUTPUT: «(Map.new((one => 0, two => 1)) Map.new((one => 0, two => 1)))»
元类
要测试给定类型对象是否为 enum
,请 .HOW
针对 Metamodel::EnumHOW 测试元对象方法,或者仅针对该 Enumeration
角色进行测试。
enum E(<a b c>);
say E.HOW ~~ Metamodel::EnumHOW; # OUTPUT: «True»
say E ~~ Enumeration; # OUTPUT: «True»
Methods
method enums
定义为:
method enums()
返回枚举对列表。
enum Mass ( mg => 1/1000, g => 1/1, kg => 1000/1 );
say Mass.enums; # OUTPUT: «{g => 1, kg => 1000, mg => 0.001}»
Coercion
如果要将枚举元素的值强制转换为其合适的枚举对象,请使用带有枚举名称的 coercer:
my enum A (sun => 42, mon => 72);
A(72).pair.say; # OUTPUT: «mon => 72»
A(1000).say; # OUTPUT: «(A)»
最后一个示例显示了如果没有枚举对包含它作为值会发生什么。
module
模块通常是一个或多个公开 Raku 结构的源文件,例如类,角色,grammars,子例程和变量。模块通常用于将 Raku 代码分发为可在另一个 Raku 程序中使用的库。
有关完整说明,请参阅模块。
package
Packages are nested namespaces of named program elements. Modules, classes and grammars are all types of package.
For a full explanation see Packages.
包是命名程序元素的嵌套命名空间。模块,类和语法都是所有类型的包。
有关完整说明,请参阅包。
grammar
Grammar 是用于解析文本的特定类型。Grammars 由 rule,token 和 regex 组成,它们实际上是方法,因为 grammars 是类。
有关完整说明,请参阅Grammars。
版本和作者
版权和作者身份可以通过副词 :ver<>
和 :auth<>
应用。两者都以字符串作为参数,对于 :ver
, 字符串被转换为 Version对象。查询语法版本和作者使用 .^ver
和 ^.auth
。
grammar G:ver<4.2.3>:auth<me@here.local> {}
say [G.^ver, G.^auth];
# OUTPUT: «[v4.2.3 me@here.local]»
subset
subset
声明一个会重新分配到其基类型的新类型。如果提供了 where
子句,则将针对给定的代码对象检查任何赋值。
subset Positive of Int where * > -1;
my Positive $i = 1;
$i = -42;
CATCH { default { put .^name,': ', .Str } }
# OUTPUT: «X::TypeCheck::Assignment: Type check failed in assignment to $i; expected Positive but got Int (-42)»
Subsets 可用于签名,例如通过键入下面的输出:
subset Foo of List where (Int,Str);
sub a($a, $b, --> Foo) { $a, $b }
# Only a List with the first element being an Int and the second a Str will pass the type check.
a(1, "foo"); # passes
a("foo", 1); # fails
Subsets 可以是匿名的,允许在需要 subset 的情况下进行内联放置,但名字既不需要也不值得。
my enum E1 <A B>;
my enum E2 <C D>;
sub g(@a where { .all ~~ subset::where E1|E2 } ) {
say @a
}
g([A, C]);
# OUTPUT: «[A C]»
Subsets 可用于动态检查类型,这可以与 require 结合使用。
require ::('YourModule');
subset C where ::('YourModule::C');