How to type variadic data?

So, how should I change this piece of code to make the compiler and type system happy? I was getting errors on different places of my app, but I realised that the problem must bu up in the chain, so I started to type things and I traced the problem to here:

open Standard

type col = String of string | Int of int
type row = col array
type sheet = row array

let pick_columns (a,b) (row: row): (string * int) =
  ((Array.get row a), Array.get row b)

Obviously the problem here is that pic_columns returns a tuple of col * col but it needs to be string * int, but since col can be string or int, I need to narrow it down, but I have no idea how.

Also, I don’t understand why I can’t return (row[a], row[b]), it tries to call row with a or something very weird.

To give you more context here is a real data set:

    [ "Materials", "Quantity", "Cost per unit", "Total per material", "" ],
    [ "8*8 Led matrix", 4, 6.7225, 26.89, "" ],
    [ "D1 mini ESP8266 - 2", 1, 2.205, 2.205, "" ],
    [ "5V 3A power supply", 1, 2.98, 2.98, "" ],
    [ "", "", "", "", "" ],
    [ "", "", "", "", "" ]

From here I want to pick first two columns of each row and return that tuple.

The syntax for array access is row.(a). The compiler reads row[a] as applies row to the list [a]. Concerning your type problem, I don’t see any variadic arguments? And if you start with a sum type that handles all possible case, you nee a conversion function to translate your row to a more finely typed tuple:

let type_row a b row = match row.(a), row.(b) with
| String x, Int y -> Some (x, y)
| _ -> None

And the conversion function might fail, thus it needs to either raises an exception or returns an option.

That looks like exactly what I need! thanks!
I think the syntax for array access I saw it on a reason focused docs (albeit I changed the syntax to ocaml, this was hand-written).
I think I can use your function with some kind of flatMap to get rid of the rows that doesn’t match my desired shape.

Rather than flat_map, there is filter_map for those use cases in recent versions of the standard library.

Yes, that is what I wanted to say, sorry.
This is what I ended doing:

let pick_columns (a,b) (row: row): (string * int) option =
  match row.(a), row.(b) with
  | String x, Int y -> Some (x, y)
  | _ -> None

let toMap sheet : sheet_map = 
  (Array.filterMap sheet ~f:(pick_columns (0,1))) 
  |> Map.String.ofArray

It compiles, which gives me good feelings.
This was a very elegant solution, thank you very much!!!

You might be also interested in how we tackled with this problem in the Ogre library, which enables typed queries and employs variadic functions. Here is the link to the implementation.

Thanks for the links @ivg, I’ll take a look.

This is what I had to add just to properly match the js code with ADTs. It is a lot of boilerplate and I don’t understand exactly how it works, but I checked that it works on real world. I’m not sure why I need an internal module inside my module, why do I need two functions inside the module (number, and string seem useless to me).

module Number_or_string : sig 
    type t 
    type case = 
        | Int of int 
        | String of string
    val number : int -> t 
    val string : string -> t 
    val classify : t -> case             
end = struct 
    type t = 
        | Any : 'a -> t 
    type case = 
        | Int of int 
        | String of string
    let number (v : int) = Any v 
    let string (v : string) = Any v     
    let classify (Any v : t) : case = 
        if Js.typeof v = "number" then Int (Obj.magic v  : int)
        else String (Obj.magic v : string)

type row = Number_or_string.t array
type sheet = row array
type sheet_map = int Standard.Map.String.t 

let pick_columns (a,b) (row: row): (string * int) option =
  match Number_or_string.classify(row.(a)), Number_or_string.classify(row.(b)) with
  | String x, Int y -> Some (x, y)
  | _ -> None