Функция привязки ReasonML с конфигом, имеющим фиксированные строковые значения - PullRequest
2 голосов
/ 24 марта 2019

Допустим, у меня есть эта функция в 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);

Значения color и other являются целыми числами, а не строками.

Ответы [ 3 ]

1 голос
/ 25 марта 2019

Атрибуты @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, ());
1 голос
/ 25 марта 2019

Это случай идиомы JavaScript для именованных параметров (объекты с необязательными полями), которые необходимо адаптировать к идиоме OCaml / ReasonML (функции с фактическими помеченными параметрами).Вы сделали бы это в три этапа.Шаг 1, как показал Гленн, определите внешнее для конфигурации:

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, ());
0 голосов
/ 25 марта 2019

Это потому, что на самом деле эти значения вариантов , вместо того, чтобы пытаться сделать его в точности как JavaScript, я бы предпочел попробовать что-то более идиоматическое для Reason:

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

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

А затем внутри вашей функции фактически возвращаются строки (если это то, что вам действительно нужно) путем сопоставления с шаблоном с правильными значениями, предоставленными config.

См. Рабочий код здесь .

Надеюсь, это поможет!

...