Raku 类型的定义§
类型通过创建一个类型对象来定义一个新对象,该对象提供了一个接口来创建对象实例或根据其检查值。任何类型对象都是 Any
或 Mu
的子类。内省方法通过从这些基类和内省元方法 (.^) 继承来提供。在编译时或在运行时使用 元对象协议 通过以下类型声明符之一将新类型引入当前范围。所有类型名称在其范围内必须唯一。
默认类型§
如果用户未提供类型,Raku 假设类型为 Any
。这包括 容器、基类、参数 和返回类型。
my = 1;= Nil;say .^name;# OUTPUT: «Any»;say C.^parents(:all);# OUTPUT: «((Any) (Mu))»
对于容器,默认类型为 Any
,但默认类型约束为 Mu
。请注意,绑定替换了容器,而不仅仅是值。在这种情况下,类型约束可能会发生变化。
类型对象§
要测试对象是否为类型对象,请对使用 类型笑脸 或 .DEFINITE
方法约束的类型使用 智能匹配
my = Int;say ~~ Mu;# OUTPUT: «True»say not .DEFINITE;# OUTPUT: «True»
如果调用者是实例,.DEFINITE
将返回 True
。如果返回 False
,则调用者是类型对象。
未定义§
未定义对象在 Raku 中维护类型信息。类型对象用于表示未定义和未定义值类型。要提供一个常规的未定义值,请使用 Any
。如果需要与 Any
(容器和参数的默认类型)进行区分,请使用 Mu
。
按照惯例,由 .CREATE 创建的对象实例被定义。方法 .defined 将返回 Bool::True
以指示已定义。该规则的例外是 Nil
和 Failure
。请注意,任何对象都可以重载 .defined
,因此可以携带附加信息。此外,Raku 明确区分已定义和真值。即使许多值带有错误或空值的含义,它们也是已定义的。这些值包括 0
、Bool::False、()(空列表)和 NaN
。
值可以在运行时通过 mixin 变为未定义。
my Int = 1 but role :: ;say // "undefined";# OUTPUT: «undefined»
要测试已定义,请调用 .defined
,使用 //、with/without 和 签名。
强制转换§
将一种类型转换为另一种类型是使用与目标类型同名的强制转换方法完成的。此惯例由 签名 强制执行。源类型必须知道如何将自身转换为目标类型。要允许内置类型将自身转换为用户定义的类型,请使用 augment 或 MOP。
use MONKEY-TYPING;augmentmy = 10;.=C;.this-is-c();# OUTPUT: «oioioioioioioioioioi‽»
Raku 提供在 Cool
中定义的方法,以便在应用进一步操作之前转换为目标类型。大多数内置类型都派生自 Cool
,因此可能会提供可能不需要的隐式强制转换。用户有责任注意无陷阱地使用这些方法。
my = "123.6";say .round;# OUTPUT: «124»say <a b c d>.starts-with("ab");# OUTPUT: «False»
类型声明符§
类型声明符在给定作用域中引入新类型。嵌套作用域可以用 ::
分隔。如果尚未存在此类作用域,将自动创建新 包。
;put Foo::Bar::.keys;# OUTPUT: «C»
前向声明 可以提供一个仅包含 ...
的块,即 "存根" 运算符。编译器将在当前作用域的末尾检查类型是否已定义。
# many lines later
class
§
class
声明符创建编译时构造,编译成类型对象。后者是一个简单的 Raku 对象,并提供通过执行初始化程序和子方法来构造实例的方法,以使用值填充类中声明的所有属性,以及任何父类。初始化程序可以随属性声明或在构造函数中提供。了解如何运行它们是 Metamodel::ClassHOW
的职责。这是在 Raku 中构建对象的唯一神奇部分。默认父类型是 Any
,它又从 Mu
继承。后者提供默认构造函数 .new
,按照惯例这样命名。除此之外,.new
不具有任何特殊含义,也不会以任何特殊方式对待。
有关如何使用类的更多信息,请参阅 类和对象 教程。
Mixins§
由 class
引入的类型可以在运行时使用 infix:<but> 进行扩展。不会修改原始类型,而是返回一个新的类型对象,并且可以将其存储在容器中,该容器可以针对原始类型或混合的角色成功进行类型检查。
my R = A but R;my = .new;.m;say [ ~~ R, ~~ R];# OUTPUT: «oi‽[True True]»
内省§
元类§
要测试给定的类型对象是否为类,请针对 Metamodel::ClassHOW
测试元对象方法 .HOW
。
;say C.HOW ~~ Metamodel::ClassHOW;# OUTPUT: «True»
私有属性§
私有 Attribute
使用任何小树枝 $!
、@!
和 %!
进行寻址。它们不会自动生成公共访问器方法。因此,它们无法从定义它们的类外部进行更改。
;say (.name, .package, .has_accessor) for C.new.^attributes;# OUTPUT: «($!priv (C) False)»
方法§
method
声明符定义类型为 Method
的对象,并将它们绑定到类作用域中提供的名称。类中的方法默认情况下是 has
作用域。our
作用域的方法默认情况下不会添加到方法缓存中,因此无法使用访问符 sigil $.
调用它们。使用它们的完全限定名称和调用者作为第一个参数来调用它们。
继承和 multis§
子类中的普通方法不会与父类的 multis 竞争。
is Amy int ;B.new.m();# OUTPUT: «B::Int»
仅方法§
要明确说明方法不是 multi 方法,请使用 only
方法声明符。
;# OUTPUT: «X::Comp::AdHoc: Cannot have a multi candidate for 'm' when an only method is also in the package 'C'»
子方法 BUILD§
BUILD
子方法 由 .bless(间接地)调用。它用于设置类的私有和公有属性,并接收传递给 .bless
的所有命名属性。在 Mu
中定义的默认构造函数 .new 是调用它的方法。鉴于 BUILD
中不可用公有访问器方法,您必须改用私有属性表示法。
;C.new.say; C.new('answer').say;# OUTPUT: «C.new(attr => 42)# C.new(attr => "answer")»
后备方法 §
当其他解析名称的方法没有产生结果时,将调用具有特殊名称 FALLBACK
的方法。第一个参数保存名称,所有后续参数从原始调用中转发。支持多方法和 子签名。
;Magic.new.simsalabim(42, "answer");# OUTPUT: «simsalabim called with parameters ⌈\(42, "answer")⌋»
保留的方法名称§
一些内置内省方法实际上是编译器提供的特殊语法,即 WHAT
、WHO
、HOW
和 VAR
。使用这些名称声明方法将静默失败。动态调用将起作用,这允许从外部对象调用方法。
;say A.new.WHAT; # OUTPUT: «(A)»say A.new."WHAT"() # OUTPUT: «ain't gonna happen»
包作用域中的方法§
任何 our
作用域方法都将在类的包作用域中可见。
;say C::.keys# OUTPUT: «(&packaged)»
使用同名变量和方法设置属性§
您可以节省一些输入,而不是编写 attr => $attr
或 :attr($attr)
,如果您使用与属性同名的变量(或方法调用)设置属性
;;my = B.new.m;say .i; # OUTPUT: «answer»
由于 $.i
方法调用名为 i
,并且属性也名为 i
,因此 Raku 允许我们使用快捷方式。这同样适用于 :$var
、:$!private-attribute
、:&attr-with-code-in-it
等。
特征 is nodal
§
标记 List
方法,以指示超运算符不要进入内部 Iterable
以调用此方法。此特征通常不是最终用户会使用的东西,除非他们正在对核心 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)»
handles
§
multi trait_mod:<handles>(Attribute , )
应用于类属性的 特征 handles
将所有调用委托给属性同名方法。由属性引用的对象必须已初始化。可以提供对调用委托到的对象的类型约束。
is A;say C.new(B.new).m(); # OUTPUT: «B::m has been called.»
除了方法名称,还可以提供Pair
(用于重命名)、名称列表或Pair
、Regex
或Whatever
。在后一种情况下,类本身及其继承链中现有的方法将优先。如果甚至要搜索本地FALLBACK
,请使用HyperWhatever
。
say C.new.m2; # OUTPUT: «A::m2 has been called.»say D.new.m1; # OUTPUT: «A::m1 has been called.»say E.new.em1; # OUTPUT: «A::m1 has been called.»say F.new.m1; # OUTPUT: «A::m1 has been called.»say F.new.m2; # OUTPUT: «A::m2 has been called.»
trait is
§
multi trait_mod:<is>(Mu , Mu )
is
trait接受一个类型对象,该对象将在其定义中作为类的父类添加。为了允许多重继承,该trait可以应用多次。向类添加父类会将它们的方法导入目标类。如果同一方法名称出现在多个父类中,则第一个添加的父类将胜出。
如果没有提供is
trait,则Any
的默认值将用作父类。这强制所有 Raku 对象具有相同的基本方法集,以提供用于内省和强制转换为基本类型的接口。
say A.new.^parents(:all).raku;# OUTPUT: «(Any, Mu)»is A is Bsay C.new.from-a();# OUTPUT: «A::from-a»
is rw
§
sub trait_mod:<is>(Mu , :!)
类上的is rw
trait将在该类的所有公共属性上创建可写访问器方法。
is rw;my = C.new.a = 42;say ; # OUTPUT: «42»
trait is required
§
multi trait_mod:<is>(Attribute , :!)multi trait_mod:<is>(Parameter , :!)
将类或角色属性标记为必需。如果在对象构造时未初始化属性,则会抛出X::Attribute::Required
。
say Correct.new(attr => 42);# OUTPUT: «Correct.new(attr => 42)»C.new;CATCH# OUTPUT: «X::Attribute::Required => The attribute '$!attr' is required, but you did not provide a value for it.»
请注意,具有私有属性的类将给出相同的错误
D.new;CATCH# OUTPUT: «X::Attribute::Required => The attribute '$!attr' is required, but you did not provide a value for it.»
您可以将原因作为参数提供给is required
;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
提供继承,而不受重新分派的影响。
hides A;B.new.m; # No outputB.new.n; # OUTPUT: «i am hidden»
trait is hidden
允许类隐藏自己,不受重新分派的影响。
is hiddenis AB.new.m; # No outputB.new.n; # OUTPUT: «i am hidden»
使用is hidden
声明的类还会生成略有不同的方法签名。为了便于重新分派,典型的方法会自动提供一个额外的*%_
参数,该参数捕获额外的命名参数。由于使用is hidden
声明的类不参与重新分派,因此它们的方法不会接收此额外参数。
trait trusts
§
要允许一个类访问另一个类的私有方法,请使用trait trusts
。可能需要对受信任类进行前向声明。
;;;say B.new.change;# OUTPUT: «B.new(a => A.new(foo => 42))»
扩充类§
要在编译时向类添加方法和属性,请在类定义片段前面使用augment
。编译器将要求在同一作用域的早期使用pragma use MONKEY-TYPING
或use MONKEY
。请注意,可能会产生性能影响,因此需要使用pragma。
use MONKEY; augment;my = "42";.mark(set => "answer");say .mark# OUTPUT: «answer»
在类片段中可以做什么有一些限制。其中之一是将方法或子项重新声明为多项。尚未实现使用添加的属性。请注意,添加仅在其命名参数中不同的多候选项将使该候选项添加到已定义的候选项之后,因此分派器不会选择它。
role
§
角色是类片段,它允许定义由类共享的接口。role
声明符还引入了一个类型对象,可用于类型检查。角色可以在运行时和编译时混合到类和对象中。role
声明符返回创建的类型对象,从而允许定义匿名角色和就地混合。
does Serializedoes Serializemy Serialize ;.push: A.new;.push: B.new;say ».to-string;# OUTPUT: «[A<57192848> B<57192880>]»
使用 ...
作为方法正文的唯一元素来声明一个方法为抽象方法。任何混合了此类方法的类都必须对其进行重载。如果在编译单元结束之前没有重载该方法,则会抛出 X::Comp::AdHoc
。
EVAL 'role R { method overload-this(){...} }; class A does R {}; ';CATCH# OUTPUT: «X::Comp::AdHoc Method 'overload-this' must be implemented by A because it is required by roles: R.»
自动转换§
可以使用角色来代替类来创建对象。由于角色在运行时不存在,因此会创建一个同名类,该类将针对角色成功进行类型检查。
;R.new.^mro.say;# OUTPUT: «((R) (Any) (Mu))»say R.new.^mro[0].HOW.^name;# OUTPUT: «Perl6::Metamodel::ClassHOW»say R.new ~~ R;# OUTPUT: «True»
特征 does
§
特征 does
可以应用于角色和类,从而提供编译时混合。要引用尚未定义的角色,请使用前向声明。混合了角色的类的类型名称不会反映混合,但类型检查会反映混合。如果在多个混合角色中提供了方法,则首先定义的方法优先。可以提供用逗号分隔的角色列表。在这种情况下,将在编译时报告冲突。
;does R2 ;;does R1 ;say [C ~~ R1, C ~~ R2];# OUTPUT: «[True True]»
参数化§
可以在角色名称后面的 []
中为角色提供参数。支持 类型捕获。
[] ;does R["default"] ;my = C.new;say ;# OUTPUT: «C.new(a => "default")»
参数可以具有类型约束,where
子句不支持类型,但可以通过 subset
实现。
;;where * ~~ A|B;[A-or-B ::T] ;R[A.new].new;
可以提供默认参数。
[ = fail("Please provide a parameter to role R")] ;my = 1 does R;CATCH# OUTPUT: «X::AdHoc: Could not instantiate role 'R':Please provide a parameter to role R»
作为类型约束§
可以在任何需要类型的地方使用角色作为类型约束。如果角色与 does
或 but
混合,则其类型对象将添加到相关对象的类型对象列表中。如果使用角色代替类(使用自动转换),则会将自动生成的类的类型对象(名称与角色相同)添加到继承链中。
[ = fail('Please provide a SI unit quantifier as a parameter to the role Unitish')]does Unitish[<s>]does Unitish[<m>]does Unitish[<g>]sub postfix:<s>(Numeric )sub postfix:<m>(Numeric )sub postfix:<g>(Numeric )sub postfix:<kg>(Numeric )constant g = 9.806_65;does Unitish[<N>]multi N(SI-kilogram , SI-meter , SI-second --> SI-Newton )multi N(SI-kilogram --> 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
角色;此角色为每个元素包含一个索引,该索引在地图上创建顺序。
符号的字符串化,它在字符串上下文中自动完成,并且完全等于其名称,这也是枚举对的键。
( name1 => 1, name2 => 2 );say name1, ' ', name2; # OUTPUT: «name1 name2»say name1.value, ' ', name2.value; # OUTPUT: «1 2»
比较符号将使用类型信息和枚举对的值。作为值类型 Num
和 Str
受支持。
( name1 => 1, name2 => 2 );sub same(Names , Names )say same(name1, name1); # OUTPUT: «True»say same(name1, name2); # OUTPUT: «False»my = name1;say ~~ Names; # OUTPUT: «True»say .^name; # OUTPUT: «Names»
所有键必须是相同类型。
( mg => 1/1000, g => 1/1, kg => 1000/1 );say Mass.enums;# OUTPUT: «Map.new((g => 1, kg => 1000, mg => 0.001))»
并且你可以使用任何种类的符号
<♣ ♦ ♥ ♠>;
只要你使用完整语法引用该符号
say Suit::<♣>; # OUTPUT: «♣»my = '♥';say Suit::«»; # OUTPUT: «♥»
尝试在没有该语法的情况下访问 unicode 枚举键将导致错误
say ♣ ; # OUTPUT: «(exit code 1) ===SORRY!===Argument to "say" seems to be malformed…
如果没有给定值,则 Int
将被假定为值类型,并且从零开始的每个键递增一。作为枚举键类型 Int
、Num
、Rat
和 Str
受支持。
<one two three four>;say Numbers.enums;# OUTPUT: «Map.new((four => 3, one => 0, three => 2, two => 1))»
可以提供不同的起始值。
«:one(1) two three four»;say Numbers.enums;# OUTPUT: «Map.new((four => 4, one => 1, three => 3, two => 2))»
你也可以使用初始化器的 () 形式来执行此操作,但需要引用没有值的键
(one => 1,'two','three','four');
枚举也可以是匿名的,与命名 enum
的唯一区别在于你无法在 Signature
中使用它或声明变量。
my = enum <one two three>;say two; # OUTPUT: «two»say one.^name; # OUTPUT: «»say .^name; # OUTPUT: «Map»
有各种方法可以访问已定义符号的键和值。所有这些都将值转换为 Str
,这可能不是理想的。通过将枚举视为包,我们可以获取键的类型列表。
<one two>;my = E::.values;say .map: *.raku;# OUTPUT: «(E::one E::two)» or «(E::two E::one)»
请注意,如上所示,枚举的迭代顺序没有保证。这是因为“迭代”枚举的方法不会直接迭代枚举(毕竟,枚举不可迭代)。相反,这些迭代方法会创建一个 Map
,其键和值与枚举相同。因为 Map
仅提供无序迭代,所以枚举上的迭代方法也是如此。如果你需要按顺序迭代枚举,则可以对其 value
进行排序,例如使用 E.enums.sort(*.value)
。
通过使用 () 括号,可以使用任何任意动态定义的列表来定义枚举。该列表应由 Pair 对象组成
例如,在文件 config
中,我们有
a 1 b 2
我们可以使用此代码使用它创建一个枚举
('config'.IO.lines.map());say ConfigValues.enums; # OUTPUT: «Map.new((a => 1, b => 2))»
首先,我们从 config
文件中读取行,使用 words
方法拆分每一行,并为每一行返回结果对,从而创建一个 Pair 列表。
键入枚举§
使用显式范围声明枚举时,可以提供一个类型,该类型将用于类型检查枚举的值
my Str (foo => 'foo');
所有枚举对都键入为 Enumeration
。此外,当枚举值键入为 Numeric
、Stringy
或这两种类型的组合时,枚举对也会分别执行 NumericEnumeration
、StringyEnumeration
和 NumericStringyEnumeration
角色。这些只是确定枚举对如何使用 Str
方法字符串化。
鉴于这些类型是角色,因此你可以在声明枚举时自然地提供自己的角色,这允许你为它们提供自定义行为和状态。例如,为了更轻松地检查数字是否与位掩码枚举中的标志匹配,你可以编写一个 BitmaskEnumeration
角色,并使用 ACCEPTS
方法通过智能匹配来处理此问题
does BitmaskEnumeration (FLAG_FOO => 0b001,FLAG_BAR => 0b010,FLAG_BAZ => 0b100,);say 0b111 ~~ FLAG_FOO & FLAG_BAR & FLAG_BAZ; # OUTPUT: «True»
元类§
要测试给定的类型对象是否为enum
,请针对Metamodel::EnumHOW
或针对Enumeration
角色测试元对象方法.HOW
。
(<a b c>);say E.HOW ~~ Metamodel::EnumHOW; # OUTPUT: «True»say E ~~ Enumeration; # OUTPUT: «True»
方法§
请参阅Enumeration
角色以了解枚举类型和枚举对的可用方法。
强制转换§
如果要将枚举元素的值强制转换为其正确的枚举对象,请使用具有枚举名称的强制转换器
my (sun => 42, mon => 72);A(72).pair.say; # OUTPUT: «mon => 72»A(1000).say; # OUTPUT: «(A)»
最后一个示例显示了如果没有包含该值作为值的枚举对时会发生什么。
module
§
模块通常是一个或多个源文件,用于公开 Raku 构造,例如类、角色、语法、子例程和变量。模块通常用于将 Raku 代码作为库进行分发,可以在另一个 Raku 程序中使用。
有关完整说明,请参阅模块。
package
§
包是命名程序元素的嵌套名称空间。模块、类和语法都是包类型。
有关完整说明,请参阅包。
grammar
§
语法是专门用于解析文本的类类型。语法由规则、标记和正则表达式组成,这些实际上是方法,因为语法是类。
有关完整说明,请参阅语法。
subset
§
subset
声明一个新类型,该类型将重新分派到其基本类型。如果提供了where
子句,则将针对给定的代码对象检查任何赋值。
of Int where * > -1;my Positive = 1;= -42;CATCH# OUTPUT: «X::TypeCheck::Assignment: Type check failed in assignment to $i; expected Positive but got Int (-42)»
子集可用于签名,例如通过键入输出
of List where (Int,Str);sub a(, , --> Foo)# Only a List with the first element being an Int and the second a Str will pass the type check.a(1, "foo"); # passesa("foo", 1); # fails
如果您跳过基本类型,它将默认为Any
。因此,以下两个是等效的
where * ~~ A | B
of Any where * ~~ A | B
子集可以是匿名的,允许在需要子集但不需要或不希望名称的内联放置。
my <A B>;my <C D>;sub g( where )g([A, C]);# OUTPUT: «[A C]»
子集可用于动态检查类型,这与require结合使用时很有用。
require ::('YourModule');where ::('YourModule::C');
版本控制、作者和 API 版本。§
声明类型时,可以向其传递版本、作者和/或 API 编号,所有这些都可以随后进行内省。类型的版本控制、作者和/或 API 编号可以通过副词 :ver<>
、:auth<>
和 :api<>
分别应用。所有这些都将字符串作为参数;对于 :ver
,字符串将转换为Version
对象,对于 :api
,字符串将转换为同形异义词IntStr
对象。:auth
通常采用 hosting:ID
形式,例如 github:github-user
或 gitlab:gitlab-user
。
要查询类型的版本、作者和 API 版本,请分别使用.^ver
、.^auth
和.^api
,如下面查询class
所示。
:ver<4.2.3>:auth<github:jane>:api<1>say C.^ver; # OUTPUT: «v4.2.3»say C.^ver.parts; # OUTPUT: «(4 2 3)»say C.^auth; # OUTPUT: «github:jane»say C.^api; # OUTPUT: «1»
类似地,role
、grammar
和 module
可查询上述信息。