Hello,
Motivated by Raven, we’re thinking of having a declarative schema of a table (like csv file), and be able to define functions row-wise over them. See Declarative Pipelines by Databrics for a production use-case.
I devised the following solution
(* abstract schema *)
type _ schema =
| [] : unit schema
| (::) : 'a * 'b schema -> ('a * 'b) schema
(* table as a list conforming to some schema *)
type 'a table = ('a schema) list
(* greet users following the schema *)
let greet_users (greet : 'a schema -> string) (tbl : 'a table) : unit =
List.map greet tbl
|> List.iter (fun str -> Printf.printf "%s\n" str)
(* example *)
let () =
let my_tbl : 'a table = [
[ 1; "Alice"; Some "alice@ocaml.com" ];
[ 2; "Bob"; None ]
]
and my_greet : 'a schema -> string = function
| [ _; name; Some email ] ->
Printf.sprintf "Hello %s, your email is %s" name email
| [ _; _; None ] -> "email not found"
in
greet_users my_greet my_tbl
Edit. I am thankful for @JohnJ for fixing the example to use the schema properly.
Note ‘a table and ‘a schema in the example are inferred.
Pros.
- type
_ schemamay represent any number of columns of any type. greet_usersensures the input ofgreetfunction is consistent with the schema of the table.- function
my_greetmay be defined over any number of arguments of any type — technically it is a single argument encoding multiple arguments recursively.
Discussion.
- What do you think of this solution, in regards to expressivity and performance?
Could we design a nicer sugar syntax, so that the table’s records are like[1, “Alice”, “alice@ocaml.com”], and the function is likemy_greet id name (Some email) = ..?Could meta-programming be useful?