Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support polymorphic variants? #39

Closed
TomiS opened this issue Dec 9, 2019 · 16 comments
Closed

Support polymorphic variants? #39

TomiS opened this issue Dec 9, 2019 · 16 comments

Comments

@TomiS
Copy link

TomiS commented Dec 9, 2019

Writing this

[@decco]
type foo = [ | `bar | `baz];

gives an error: "This syntax is not yet handled by decco"

I assume this is not surprising to anyone. Just creating a ticket to show my interest in this feature. :)

@TomiS
Copy link
Author

TomiS commented Dec 11, 2019

In case someone is searching for the same thing, it is possible to do this by using custom codecs. Here's one example that converts locale strings to polyvariants

module LocaleAsString = {
  type t = [ | `en | `fi | `sv | `pt];

  let t_encode: Decco.encoder(t) =
    locale =>
      switch (locale) {
      | `en => Js.Json.string("en")
      | `fi => Js.Json.string("fi")
      | `sv => Js.Json.string("sv")
      | `pt => Js.Json.string("pt")
      };

  let t_decode: Decco.decoder(t) =
    json =>
      switch (Js.Json.classify(json)) {
      | Js.Json.JSONString(str) =>
        switch (str) {
        | "en" => `en->Belt.Result.Ok
        | "fi" => `fi->Belt.Result.Ok
        | "sv" => `sv->Belt.Result.Ok
        | "pt" => `sv->Belt.Result.Ok
        | _ => Decco.error("Locale '" ++ str ++ "' is not supported.", json)
        }
      | _ =>
        Decco.error(
          "Trying to decode a field that is not a string to locale",
          json,
        )
      };
};

And usage in the type definition

[@decco]
type myRecord = {
  locale: LocaleAsString.t,
};

@kittipatv
Copy link

kittipatv commented Dec 31, 2019

There is a less verbose way to do this. According to BuckleScript doc, [@bs.deriving jsConverter] can be used to convert polymorphic variant to JS string enum. Quoting from the doc:

[@bs.deriving jsConverter]
type fruit = [
  | `Apple
  | [@bs.as "miniCoconut"] `Kiwi
  | `Watermelon
];

let appleString = fruitToJs(`Apple); /* "Apple" */
let kiwiString = fruitToJs(`Kiwi); /* "miniCoconut" */

Those functions can be used as the base for conversion. In DeccoHelper.re

open Belt;

let encode = (x, ~tToJs) => tToJs(x) |> Decco.stringToJson;
let decode = (x, ~tFromJs, ~name) =>
  Decco.stringFromJson(x)
  ->Result.map(tFromJs)
  ->Result.flatMap(y =>
      switch (y) {
      | Some(value) => Ok(value)
      | None => Decco.error("Unknown " ++ name, x)
      }
    );

Then, we can use the helpers like this in Fruit.re (I changed fruit type to t, just for convenience):

[@bs.deriving jsConverter]
type t = [
  | `Apple
  | [@bs.as "miniCoconut"] `Kiwi
  | `Watermelon
];

let t_encode = DeccoHelper.encode(~tToJs);
let t_decode = DeccoHelper.decode(~tFromJs, ~name="Fruit");

@kittipatv
Copy link

This can be made more convenient with functor. Add this to DeccoHelper.re

module type PolymorphicVariantWithJsConverter = {
  type t;
  let tToJs: t => string;
  let tFromJs: Js.String.t => option(t);
  let name: string;
};

module MakePV = (PV: PolymorphicVariantWithJsConverter) => {
  include PV;

  let t_encode = encode(~tToJs);
  let t_decode = decode(~tFromJs, ~name);
};

Then, you can

module Fruit = DeccoHelper.MakePV({
  [@bs.deriving jsConverter]
  type t = [
    | `Apple
    | [@bs.as "miniCoconut"] `Kiwi
    | `Watermelon
  ];
  let name = "Fruit";
});

@TomiS
Copy link
Author

TomiS commented Dec 31, 2019

@kittipatv Nice. That is indeed much more elegant way to do it.

@yawaramin
Copy link

However note that jsConverter does not support polymorphic variants with payloads.

@farukg
Copy link

farukg commented Aug 15, 2020

Just to inform:
maybe this got much simpler at least for polyvars without payload, i guess, since bucklescript 8.2 and string literals.

// any polyvar like 
let c= `color; 
// will compile to just a normal string in Javascript
var c = "color"

source:
https://reasonml.org/blog/string-literal-types-in-reason

@davesnx
Copy link
Contributor

davesnx commented Feb 18, 2021

I'm happy to work on that @ryb73.
Is there anything that I should be aware of beforehand?

Thanks

@ryb73
Copy link
Member

ryb73 commented Feb 18, 2021

Thanks @davesnx! There are a couple general tips here: #25 (comment) #6 (comment)
PPXs are confusing, so feel free to ask questions

@Pet3ris
Copy link

Pet3ris commented Apr 8, 2021

@kittipatv is there a good workaround for polyvariants with variable tags?

E.g.,

type t = [#ENTERS | #REGULAR | #RETURNS | #FutureAddedValue(string)]

Current workaround I've used is just to manually encode these cases:

module Jump = DeccoHelper.MakePV({
  @bs.deriving(jsConverter)
  type t = [#ENTERS | #REGULAR | #RETURNS | #FutureAddedValue(string)]
  let tToJs = (val: t): string => switch val {
    | #ENTERS => "ENTERS"
    | #REGULAR => "REGULAR"
    | #RETURNS => "RETURNS"
    | #FutureAddedValue(value) => value
  }
  let tFromJs = (s: Js.String.t ): option<t> => switch s {
    | "ENTERS" => Some(#ENTERS)
    | "REGULAR" => Some(#REGULAR)
    | "RETURNS" => Some(#RETURNS)
    | other => Some(#FutureAddedValue(other))
  }
  let name = "Jump"
})

@davesnx
Copy link
Contributor

davesnx commented Apr 21, 2021

Last version v1.4.0 supports polyvariants after merging #64

Let me know if you found any issue with that

@ryb73 ryb73 closed this as completed Apr 22, 2021
@benadamstyles
Copy link

I'm quite confused by this, I must be missing something obvious, but v1.4.0 only successfully decodes arrays to poly variants, not strings.

So the following fails:

{
  "format": "abc"
}
@decco.decode
type t = {
  format: [#abc]
}

But the following works:

{
  "format": ["abc"]
}
@decco.decode
type t = {
  format: [#abc]
}

Can this be right?

@ryb73
Copy link
Member

ryb73 commented Jun 8, 2021

@benadamstyles This is correct. I think what you're looking for is described in #36

@benadamstyles
Copy link

@ryb37 Thanks for the quick response! I still don't understand – why are polyvariants, which are single values, being encoded to arrays?

@ryb73
Copy link
Member

ryb73 commented Jun 8, 2021

Basically it's because they're not necessarily single values. For example
(in Reason syntax because I'm not familiar with ReScript):

[@decco] type v = `something(int, string);
`something(123, "abc") |> v_encode; /* result: ["something", 123, "abc"] */

Part of me wishes in retrospect I'd have made it so that variants without
attached data would be encoded to strings instead of arrays, but
unfortunately that ship has sailed. I don't use Reason anymore so I'm not
really doing any more Decco development myself, but would be happy to take
a PR for issue #36.

@benadamstyles
Copy link

Ok thanks, I understand now. I'm not sure how #36 would help because that's about renaming, if I understand correctly, whereas I need a completely different representation... but if that's a breaking change I don't see any good way forward so I'll stick with manual decoders for now. Thanks again for the quick responses!

@ryb73
Copy link
Member

ryb73 commented Jun 9, 2021

Hmm, it's possible I misunderstood what @mrmurphy was requesting, but my interpretation of #36 is that it would use a string instead of an array

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants