具有固定字符串值的配置的 ReasonML 绑定函数

ReasonML binding function with config having fixed string values

比方说,我在 Javascript 中有这个函数,它可以根据适当的配置生成字符串:

function func(config) {
  // ...
}

此外,我们假设 config 变量具有如下结构(所有这些都不能提供给函数调用):

{
  "color": string,  // can be: "blue", "red", "green"
  "number": int,    // can be: any number
  "other": string,  // can be: "x", "y"
}

如何为此创建适当的绑定?我坚持:

[@bs.deriving abstract]
type options = {
  [@bs.optional]
  color: [@bs.string] [ | `blue | `red | `green ]
  [@bs.optional]
  number: int,
  [@bs.optional]
  other: [@bs.string] [ | `x | `y ]
}

[@bs.module]
external func: options => string = "func";

但是尝试这样使用时它不起作用:

let config = MyModule.config(
  ~color=`blue,
  ~number=123,
  ~other=`x
);

let value = MyModule.func(config);

colorother 值是整数,不是字符串。

这是因为在现实中,这些值是variants,而不是试图让它完全像JavaScript,我宁愿尝试更多的东西Reason 的惯用语:

type color = Blue | Green | Red;
type coords = X | Y;
type config = {
  color,
  coords,
  number: int
};

let func = (config: config) => "something"

然后在你的函数内部实际上 return 字符串(如果这是你真正需要的)通过模式匹配提供给 config.

的正确值

查看工作代码here

希望对您有所帮助!

@bs 属性通常是未经深思熟虑的 hack,您不应该期望它们能与其他属性一起使用,或者实际上与文档解释或显示示例之外的任何东西一起使用。但是,如果在不希望使用的地方使用某个属性,您通常至少会收到有关该属性未被使用的警告,而您的代码确实如此。

@bs.string 特别是仅适用于外部最外层的类型,即其值将直接传递给外部函数的类型。还有一种使用外部函数创建 JavaScript 对象的方法,它也恰好使用较少的魔法并让您对 API 有更多的控制权。据我所知,与 @bs.deriving 相比,唯一的缺点是您不能使用 @bs.as 之类的东西覆盖字段名称。它们必须是有效的 OCaml 标识符。

这是您使用带有 @bs.obj 注释的外部函数实现的示例:

type options;
[@bs.obj] external options : (
  ~color:[@bs.string] [`blue | `red | `green]=?,
  ~number:int=?,
  ~other:[@bs.string] [`x | `y]=?,
  unit
  ) => options = "";

要使用它,您可以像 @bs.deriving:

一样调用它
let config = options(~color=`blue,~number=123, ~other=`x, ());

但即便如此,我也遇到过传递整数值而不是字符串的边缘情况。出于这个原因,我倾向于完全避免多态变体属性,而是使用普通变体和转换函数。这具有更加惯用、更好地融合以及与非 BuckleScript 代码更具互操作性的额外好处。

使用此方法,您的示例可能如下所示:

type color = Blue | Red | Green;
let colorToString = fun
  | Blue => "blue"
  | Red => "red"
  | Green => "green";

type other = X | Y;    
let otherToString = fun
  | X => "x"
  | Y => "y";

[@bs.obj] external options : (
  ~color:string=?,
  ~number:int=?,
  ~other:string=?,
  unit
  ) => options = "";

[@bs.module] external func: options => string = "func";

let func = (~color=?, ~number=?, ~other=?, ()) =>
    func(options(
      ~color = ?Belt.Option.map(color, colorToString),
      ~number?,
      ~other = ?Belt.Option.map(other, otherToString),
      ()));

let config = func(~color=Blue,~number=123, ~other=X, ());

这是命名参数(具有可选字段的对象)的 JavaScript 习语的情况,需要适应 OCaml/ReasonML 习语(具有实际标记参数的函数)。您将分三步完成此操作。第 1 步,如 Glenn 所示,为配置定义一个外部:

type config;
[@bs.obj] external config: (
  ~color:[@bs.string] [`blue | `red | `green]=?,
  ~number:int=?,
  ~other:[@bs.string] [`x | `y]=?,
 unit,
) => config = "";

第 2 步,使用配置对象的 JavaScript 样式绑定到 JavaScript 函数:

[@bs.val] external func: config => string = "";

第 3 步,将 JavaScript 函数绑定包装在带有标记参数的 OCaml 惯用函数中:

let func(~color=?, ~number=?, ~other=?, ()) = ()
  |> config(~color?, ~number?, ~other?)
  |> func;

你可以这样使用它:

let result = func(~color=`blue, ());