BuckleScript 中的内联值
BUCKLESCRIPT 提供了一个便捷的功能,可以帮助您更安全地调用 JavaScript 函数:将参数值限制为特定的字符串。假设您有一个 JavaScript 函数(例如 React 组件):
// src/rbtree.js
/** @param node a node.
@param color can be either 'red' or 'black'.
@return the painted node. */
function paint(node, color) { ... }
在 BuckleScript 中,你可以像这样对该函数进行建模:
// src/Rbtree.re
type node;
[@bs.module "./rbtree"] external paint: (
node,
[@bs.string] [`red | `black],
) => node = "";
特殊类型:
[@bs.string] [`red | `black]
……告诉 BuckleScript 必须接受以下值:
`red
`black
……但要将它们转换为输出 JavaScript 所需的字符串表示形式。(顺便一提,这些值被称为多态变体。)这样,您就可以精确控制最终输出的字符串,并确保 JavaScript 函数被正确调用。相关文档请参见:https://bucklescript.github.io/docs/en/function#constrain-arguments-better
重用问题
上述方法的缺点在于特殊类型:
[@bs.string] [`red | `black]
……不能被捕获为命名类型并重复使用——即使省略注解也不行。它是一种特殊的语法结构,每次使用时都必须按字面值输入。
但是,你使用的库中的多个函数可能需要完全相同的字符串作为输入参数。因此,你需要为所有函数重复编写这段样板代码。对于大型库来说,这种重复会变得非常繁琐(而且容易出错)。
“私有类型”解决方案
您也可以为颜色参数定义一个私有类型:
module Color: {
type t = pri string;
let red: t;
let black: t;
} = {
type t = string;
let red = "red";
let black = "black";
};
[@bs.module "./rbtree"] external paint: (node, Color.t) => node = "";
该类型Color.t被定义为“私有字符串”,这意味着你不能创建该类型的值,Color.t但你可以将其转换为该string类型。而且,这确实是string底层实现。现在,用户只能传入已定义的值(由编译器强制执行):` Color.redString` 或 ` Color.blackString`。JavaScript 中的输出值将分别为 `String`'red'和 `String` 'black'。最棒的是,`String`Color.t是一个真正的类型,而不是语法结构,它可以在函数之间传递和使用。
这个方案只有一个小问题:由于预先分配了所有允许的值并将其包含在绑定库中,因此会给库绑定引入一些额外的运行时开销。当发送到浏览器的每一个 JavaScript 字节都至关重要时,尽可能减少这种开销就显得尤为重要。
“内联”技术
幸运的是,BuckleScript 最近发布了显式内联值的功能:https://bucklescript.github.io/blog/2019/04/09/release-schedule
请注意,我特意强调“显式”,因为 BuckleScript 一直以来都非常擅长内联(“常量折叠”)任何它可以在编译时计算的值。例如,尝试编译以下代码:let test = 2 + 2;。您将得到输出var test = 4;。而这只是一个简单的示例。
因此,借助显式内联功能,您实际上可以将颜色值标记为内联:
module Color: {
type t = pri string;
[@bs.inline "red"] let red: t;
[@bs.inline "black"] let black: t;
} = {
type t = string;
[@bs.inline] let red = "red";
[@bs.inline] let black = "black";
};
Color.red这告诉 BuckleScript 将`and`的使用Color.black与给定的字面值内联。如果你想知道这种重复是否真的必要——是的,但它确实起到了自我检查的作用,所以总的来说还不错。
现在,这种绑定引入的额外运行时开销几乎为零。您应该会在 JavaScript 输出中看到一个空的“模块” ——仅此而已。只有在运行时使用Color这些值时,才会产生相应的开销。
顺便一提,这个属性只支持 `int` 、`int` 和`int`[@bs.inline]这三种类型的值。所以它虽然有局限性,但应该能够涵盖你通常会传递给函数的大部分 JavaScript 字面量。stringintbool