Unwrap array of polymorphic variants?

I’m trying to bind to a function that takes an array, with each element being a string or array of strings. I’m using polymorphic variants to model those elements and trying to unwrap each value in the array but not sure if I’m doing something wrong or if it’s not possible right now. I can get around this with an extra function call and some magic conversions but would be great if I could do it with zero cost bindings. As a simple example here’s a copy of the documentation example replacing the int variant with a string array which works fine:

@val                                                                                                                              
external padLeft: (                                                                             
  string,                                                                          
  @unwrap                                                                          
  [                                                                                                                                  
    | #Str(string)                                                                 
    | #StrArr(array<string>)                                                                               
  ],                                                                                           
) => string = "padLeft"                                                                        
                                                                                          
padLeft("Hello World", #Str("Message from ReScript: "))->ignore                    
padLeft("Hello World", #StrArr(["yup", "nope"]))->ignore

Results in this output:

padLeft("Hello World", "Message from ReScript: ");

padLeft("Hello World", [
      "yup",
      "nope"
    ]);

Here’s what I tried that could compile:

@val                                           
external padLeft: (
  string,
  array<
    @unwrap              
    [                                             
      | #Str(string)                  
      | #StrArr(array<string>)
    ],           
  >,                     
) => string = "padLeft"
                 
padLeft("Hello World", [#Str("Message from ReScript: ")])->ignore                   
padLeft("Hello World", [#StrArr(["yup", "nope"])])->ignore

But the annotation doesn’t have any effect:

padLeft("Hello World", [{
        NAME: "Str",                              
        VAL: "Message from ReScript: "
      }]);                    
                 
padLeft("Hello World", [{
        NAME: "StrArr",
        VAL: [   
          "yup",                                                                    
          "nope"                                                                                                                                                                                                                                                              
        ]
      }]);

Thanks in advance for any help with this!

You can use a little trick to model this more dynamic JS input requirement. Make a Padding module with an abstract type that you can create from either a string or from a string array. Then make this Padding.t type be the second parameter of the padLeft external binding. This is safe because you’re passing the values into JavaScript. E.g.:

module Padding = {
  type t

  external string: string => t = "%identity"
  external array: array<string> => t = "%identity"
}

@val
external padLeft: (string, array<Padding.t>) => string = "padLeft"

This can be used like:

padLeft("Hello World", [Padding.string("Message from ReScript: ")])
padLeft("Hello World", [Padding.array(["yup", "nope"])])
4 Likes

Thank you, this is much more elegant than what I had before. By the way, could you tell me how you were able to add syntax highlighting to your code snippet?

Sure. You can use three backticks, followed by a code block, followed by three backticks again. This is shown here: https://commonmark.org/help/

1 Like