Skip to main content

Types and Variables(类型与变量)

Types and Variables(类型与变量)

Overview(概述)

ProtoGraph 与 ProtoFlux 一样,拥有强类型、静态类型系统。这意味着程序中的每个值/节点都有一个定义良好的类型,无需运行代码即可确定。ProtoGraph 在编译时会检查类型不匹配,并将其作为错误记录下来,供程序员解决。ProtoGraph 会尝试推断程序中各值的类型,从而减少显式类型注解的需求;但为了提高可读性,建议对重要的值定义进行类型注解。

虽然 ProtoGraph/ProtoFlux 是强类型的,但 ProtoFlux 的序列化形式(.brson Resonite 记录)是弱类型的。任何节点上的任何输入/输出都可以连接到任何其他节点的输入/输出、节点甚至非 Flux 组件。当加载到 Resonite 中时,这些连接会被检查,如果连接无效,则不会在节点之间创建相应的连线(输入/输出字段变为 null)。

为了方便开发和故障排除,ProtoGraph 编译器会尝试将任意节点和值“连接”在一起,即使类型不兼容。编译时会记录错误,但仍会尽力生成 Resonite 记录。然后可以将这个部分正确的记录加载到 Resonite 中,以便进行调试或在结构正确的独立代码段上工作。

Annotations(注解)

类型注解也为代码行为提供了额外的测试:如果注解类型与推导类型不匹配,编译器将产生错误。

模块头部(输入/输出/全局)中的值始终需要类型注解。

module TypeAnnotations

// 头部值必须带有注解
in  InputInteger: int

out Times2: int
out Times3: int

where {
    // Times2 的类型被推断为 int
    Times2 = ValueAdd(InputInteger, InputInteger);

    // 但为了清晰,你也可以手动添加类型注解
    Times3: int = 
        ValueAdd<int>(InputInteger, InputInteger)
        ->ValueAdd<int>(InputInteger);
}

Literals(字面量)

ProtoGraph 能够指定字面量值并将其作为参数传递给节点。每个字面量都有自己的语法,以避免类型检查器产生歧义。编译器不会隐式强制转换字面量类型。

类型 (Type) 描述 (Description) 语法 (Syntax) 示例 (Examples)
bool true 或 false true / false true, false
char 单个字符 单引号 'A'
string 字符串 双引号 "My String"
int 32 位整数 数字 或 i 1, 1i
float 32 位浮点数 带小数点、指数或 f 3.14, 1e10, 5f
double 64 位浮点数 d 42d, -1.8e-8
long 64 位整数 L 100000L
uint 无符号 32 位整数 ui 42ui
ulong 无符号 64 位整数 uL 99999uL
short 16 位整数 s 500s
ushort 无符号 16 位整数 us 500us
byte 无符号 8 位整数 uy 91uy
sbyte 有符号 8 位整数 y 1y
decimal 有符号 128 位定点十进制数 m 1111m

Value & Object Types(值类型与对象类型)

ProtoGraph 中的类型分为两类:值类型和对象类型。在 ProtoGraph 中,这决定了类型的默认值:对象的默认值是 null,而值类型的默认值是“零”值(如 0、0.0 和 false)。

Generic Types(泛型)

以另一个类型为参数的类型称为泛型。你可以把它想象成一个接受类型并返回具体类型的函数。

信息:泛型是一种多态形式,它使你能够在多个使用场景中共享通用功能。泛型被称为参数化多态(其他形式的多态包括特设多态和子类型多态)。

Lists(列表)

多个相同类型的值可以组合成一个列表。列表是泛型的,但所有元素必须是相同的类型。列表的类型通过在包含类型名称后放置 [] 来表示,例如 string[]bool[]Slot[]

IntList: int[] = [1, 2, 3];
StringList: string[] = ["Word", "Word2"];
InvalidList: ???[] = [false, 0.2, "A String"];

Variables(变量)

与 ProtoGraph 中的普通值不同,变量是可变的:可以通过写入来改变它们。写入操作可以使用 <- 作为脉冲将新值赋给变量。ProtoGraph 中有 3 种变量,涵盖不同的作用域:

Sync(同步变量)

sync SynchronizedInteger: int;

使用 sync 声明的同步变量,利用数据模型自动与其他用户同步其值。

Store(存储变量)

store StoredInteger: int;

使用 store 声明的存储变量是持久化的,但不会与其他用户同步。

Local(局部变量)

local LocalInteger: int;

使用 local 声明的局部变量是临时的,作用域为脉冲上下文。每个脉冲上下文都有自己的局部变量,一旦脉冲链结束,局部变量将被丢弃。

Casting(类型转换)

使用 as<T> 关键字节点将一种类型强制转换为另一种类型。目标类型作为显式类型参数指定。as<T> 节点接受一个参数:要转换的值。

as<object>("my string");
->as<object>; // 使用管道运算符可使代码更易读

// 转换对象类型
ActuallySlot: object = RootSlot->as<object>;
TheSlot = ActuallySlot->as<Slot>; 

// 将值类型转换为其他兼容类型
LargerNumbersAsObject = as<object>(100->as<long> * 100L);

Globals(全局变量)

类型可以附加 global 修饰符。这些类型与普通值分开存在,没有显式转换,不能相互使用。普通值可以使用 asDrivenGlobal 转换为全局变量,并且可以使用 WriteValueToGlobal / WriteObjectToGlobal 写入全局变量。

UserValue = LocalUser;
TrueValue = true;
SecondsTimer(
    1.0,
    UpdatingUser=UserValue->asDrivenGlobal,
    SkipIfNull=asDrivenGlobal(TrueValue),
    OnUpdate=ImpulseDisplay);

Null Values(空值)

对象类型和引用类型的默认值是 null(表示没有值)。使用 null<T> 关键字为没有任何内容的类型创建一个值。不能混用不同类型的 null:如果创建了 null<string>,就不能将其用于需要 Asset 的地方。

// 定义空值 Null
NullString = null<string>;
NullSlot = null<Slot>;

// 你可以将它们用作名义类型,但不同节点对空值的处理方式不同
NullString->ConcatenateString("RealString")->display; // displays null
display(NullSlot == RootSlot) // displays false

警告:ProtoFlux 中的所有对象/引用都是可为空的/可选的(它们可能没有具体的值)。ProtoFlux 运行时会自动为你检查这一点,并通过代码传播默认值。虽然你不会像在其他编程语言中那样遇到因 null 引发的异常,但如果一个你期望存在的值实际上是 null,则很容易出现逻辑错误。

Binary, Hexadecimal, & Octal(二进制、十六进制和八进制)

可以使用二进制、八进制和十六进制字符创建字面量数字值。这通过为数字添加前缀 0b(二进制)、0o(八进制)或 0x(十六进制)来实现。默认情况下,它们将被解释为 int(32 位有符号整数),并且可以通过提供后缀(如普通十进制数字那样)来更改数字类型。

DecimalNumber = 1234;
BinaryNumberUnsigned = 0b110011u;
OctalNumber = 0o1662;
HexNumber = 0xab12ef;