初识 Jvav。

从熟悉的函数和值开始,预览一次计算,再把结果应用到状态。

Jvav 仍在设计中。以下示例依据当前的规范草案编写。

函数与值

函数声明自己的参数与返回类型。这里,twice 接收一个整数,返回这个值与自身的和。

pure fun twice(x: Int): Int {
    return x + x
}

val result = twice(21)

pure 声明计算的结果来自其声明的依赖,并且不产生外部可观察的副作用。仅仅返回一个值,并不意味着函数是纯函数。

Jvav 保留了熟悉的控制流,包括条件、循环与提前返回。这些语法本身并不意味着会产生外部副作用。

可以预览的计算

transition 描述一次针对状态的计算,结果可以在应用前预览。纯函数可以算出这个结果,以 transition T 返回,而不修改原来的状态。

pure fun swap(
    pair: transition Pair
): transition Pair {
    return {
        a = pair.b
        b = pair.a
    }
}

输入提供特定 pair 的旧状态。两次读取都指向这个状态:描述新的 a,不会改变 pair.a 读到的值。

结果描述 ab 的替换值。在有效应用时,未列出的字段保留原值。计算或返回这个结果,都不会修改 pair。

应用变化

调用方决定何时应用计算所得的变化。在具备必要的修改权限、且目标有效的前提下,草案采用以下写法:

val change = swap(pair)
transition(pair) = change
应用前a = 2, b = 7
应用后a = 7, b = 2

第一行计算一个值,第二行执行一个副作用。将两者分开,同一套决策逻辑就可以用于检查、模拟与执行。

已保存的 transition 表示已经完成的计算;稍后应用它时,不会重新执行计算。如果目标在此期间发生了变化,应如何处理,草案中仍标记为待定(Pending)。

查看移动示例

访问与依赖

参数类型让访问权限变得明确。view T 提供只读、不拥有所有权的访问。mut T 授予修改现有状态的权限,并受函数的副作用契约约束。

context T 声明可由调用环境提供的有类型依赖。伤害计算可以读取实体、查询战斗规则,而不改变世界:

pure fun resolve(
    damage: Damage,
    target: view Entity,
    context: context CombatContext
): DamageResult

不同的战斗规则可能产生不同的结果。即使实参由环境隐式提供,上下文也是真实的依赖,相应的访问与副作用限制仍然适用。

表示透明性

无论值来自存储,还是来自跨越调用边界的计算,普通 Vec3 参数的含义都相同。

pure fun lengthSquared(v: Vec3): Float {
    return v.x * v.x +
        v.y * v.y +
        v.z * v.z
}

lengthSquared(a + b)

当分量运算满足相应的纯计算条件时,实现可以将加法与后续计算合并,而不构造完整的中间向量。它必须保持所要求的数值结果、副作用与其他可观察行为。

这种表示选择并不意味着普通值采用惰性求值。显式的运行时延迟计算使用 defer T,属于设计中的另一部分。

继续阅读草案。

在草案中查阅精确定义、求值规则与更多示例。所有权、捕获、transition 有效性等仍需作出设计决策的问题,标记为待定(Pending)。