3
votes

Fonction de liaison ReasonML avec configuration ayant des valeurs de chaîne fixes

Disons que j'ai cette fonction en Javascript qui peut générer une chaîne basée sur une configuration correcte:

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

let value = MyModule.func(config);

aussi, supposons, que la variable config a structure comme ci-dessous (tout cela ne peut pas être donné à l'appel de fonction):

[@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";

Comment créer une liaison appropriée pour cela? Je suis coincé avec:

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

Mais cela ne fonctionne pas en essayant d'utiliser comme ceci:

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

Le couleur et autres valeurs sont des entiers, pas des chaînes.


1 commentaires

Il y a plusieurs bogues dans votre exemple de code: 1. Il y a une virgule manquante à la fin de la définition du champ color , et 2. L'appel à MyModule.config , qui techniquement aussi doit être MyModule.options , est partiellement appliqué car il manque l'argument final unit .


3 Réponses :


0
votes

En effet, en réalité, ces valeurs sont des variantes strong>, au lieu d'essayer de le rendre exactement comme JavaScript, je préférerais essayer quelque chose de plus idiomatique à Reason:

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

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

Et puis à l'intérieur de votre fonction, retournez des chaînes (si c'est ce dont vous avez vraiment besoin ) par correspondance de motif sur les valeurs correctes fournies à config.

Voir le code de travail ici . p>

it helps! p> p>


0 commentaires

1
votes

Les attributs @bs sont souvent des hacks mal pensés dont vous ne devriez pas vous attendre à bien fonctionner avec d'autres attributs, ou vraiment avec autre chose que ce que la documentation explique ou montre des exemples. Cependant, si un attribut est utilisé là où il n'est pas prévu, vous recevrez généralement au moins un avertissement indiquant que l'attribut n'est pas utilisé, ce que fait votre code.

@ bs.string en particulier ne fonctionne que sur les types au niveau le plus externe des externes, c'est-à-dire sur les types dont les valeurs seront passées directement à la fonction externe. Il existe également un moyen de créer des objets JavaScript à l'aide de fonctions externes qui utilisent également moins de magie et vous donnent beaucoup plus de contrôle sur l'API. Pour autant que je sache, le seul inconvénient par rapport à @ bs.deriving est que vous ne pouvez pas remplacer les noms de champ en utilisant quelque chose comme @ bs.as . Ils doivent être des identifiants OCaml valides.

Voici votre exemple implémenté à l'aide d'une fonction externe annotée avec @bs.obj:

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, ());

Pour l'utiliser, vous l'appelez exactement comme avec @bs.deriving:

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

Mais même avec cela, j'ai rencontré des cas extrêmes où integer les valeurs sont transmises à la place des chaînes. Pour cette raison, j'ai tendance à éviter complètement les attributs de variantes polymorphes et à utiliser à la place des variantes ordinaires avec des fonctions de conversion. Cela présente l'avantage supplémentaire d'être plus idiomatique, de mieux s'intégrer et d'être plus interopérable avec du code non BuckleScript.

Voici à quoi pourrait ressembler votre exemple en utilisant cette approche:

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


8 commentaires

Il semble en effet que les variantes polymorphes n'ont pas besoin d'être utilisées dans ces types d'exemples. Merci pour votre réponse! J'implémente une nouvelle liaison pour la bibliothèque randomColor et j'ai utilisé votre version, pour l'instant: LIEN


@KrzysztofTrzos une chose à savoir avec cette approche est que BuckleScript peut ne pas être en mesure d'optimiser toutes les fonctions de conversion. Avec l'approche des variantes polymorphes, vous «payez pour votre utilisation».


@Yawar Alors, quel est le coût de cette solution et quels problèmes peut-elle apporter?


@Yawar J'irais encore plus loin et je dirais que ce ne sera probablement pas le cas, à moins que vous ne fassiez tout dans un seul module. Mais c'est au moins le son. C'est pourquoi j'utilise cette approche pour bs-webapi et bs-fetch , qui sont conçues comme des liaisons de bas niveau et conviennent bien à @ bs.string avait-il fonctionné correctement. Cependant, il existe d'autres moyens de réduire la surcharge, comme l'utilisation de constantes au lieu de variantes comme je le fais dans bs-fetch # next , mais alors vous sacrifiez la vérification de l'exhaustivité ... Malheureusement, il n'y a pas de réponse simple ici.


De plus, si bsc avait été un peu plus intelligent, il aurait pu compiler les fonctions de conversion dans des tables de recherche assez compactes.


@glennsl est-ce que le défaut est un problème connu? Je ne l'ai jamais rencontré auparavant.


@Yawar Aucune idée, et cela a peut-être été corrigé depuis, mais il était toujours là au moment où j'ai fait au moins la branche bs-fetch # next. Mon conseil est simplement de procéder avec prudence et de ne pas tout faire immédiatement


En parcourant la source bs-fetch pour savoir ce qui pourrait l'avoir causé, je suis tombé sur une autre considération: @ bs.string ne fonctionne pas avec @ bs.get , donc si vous Vous souhaitez non seulement créer l'objet mais également accéder à ses propriétés, vous devrez toujours écrire des fonctions de conversion.



1
votes

Il s'agit d'un cas d'idiome JavaScript pour les paramètres nommés (objets avec des champs optionnels), devant être adapté à l'idiome OCaml / ReasonML (fonctions avec des paramètres étiquetés réels). Vous feriez cela en trois étapes. Étape 1, comme Glenn l'a montré, définissez un externe pour la configuration:

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

Étape 2, liez-vous à la fonction JavaScript en utilisant le style JavaScript de l'objet de configuration:

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

Étape 3, enveloppez la liaison de la fonction JavaScript dans une fonction OCaml-idiomatic avec des paramètres étiquetés:

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

Vous pouvez l'utiliser comme ceci: p >

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


0 commentaires