Атрибуты @bs
часто представляют собой плохо продуманные хаки, которые вы не должны ожидать, что они будут хорошо работать с другими атрибутами или действительно с чем-то, кроме того, что в документации объясняется или показывает примеры. Однако если атрибут используется там, где он не предназначен, вы, по крайней мере, получите предупреждение о том, что атрибут не используется, что делает ваш код.
@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, ());