hra1234
hra1234

Reputation: 409

Ocaml - writing a function who's number of arguments is determined at runtime

I want to write a function f, that takes n arguments, where n is determined at runtime, and might vary at every call to the function, for example

let's say our function f takes an integer n which is the number of args, and n args of the same type and turns them into a list:

# f 3 'a' 'b' 'c';;
- : char list = ['a'; 'b'; 'c']
# f 2 1 2;;
- : int list = [1; 2]

I thaught of something like

let f acc n x =
  if n = 0 
  then List.rev (x::acc)
  else f [x] (x - 1)

but in this case it won't work because of the type difference.

Upvotes: 3

Views: 616

Answers (2)

Maëlan
Maëlan

Reputation: 4192

Using currying, you can do something that resembles variadic functions, but you’ll have to convince the type checker. You will not be able to conveniently provide the arity of your function as a bare integer; instead, you can unary-encode the arity as a value of a GADT:

type (_, 'r) arity =
  | O : ('r, 'r) arity
  | I : ('f, 'r) arity -> (int->'f, 'r) arity

The encoding works as follows:

  • O : ('r, 'r) arity represents the arity of a “function that takes no argument” and returns an 'r;
  • I O : (int -> 'r, 'r) arity represents the arity of a function that takes an int and then returns an 'r;
  • I (I O) : (int -> int -> 'r, 'r) arity represents the arity of a function that takes two ints and then returns an 'r;
  • I (I (I O)) : (int -> int -> int -> 'r, 'r) arity is the arity of a function that takes three ints and then returns an 'r;
  • etc.

Instead of passing 3 as a first argument to your hypothetical variadic function, you would pass I (I (I O)). This value describes the sequence of arguments that the function is supposed to take (one int, then one int, then one int, then return). The function would then proceed recursively, destructing (inspecting) this description to decide what to do next You can implement your example function that builds the list of all its arguments, like so:

let rec f_aux : type f. int list -> (f, int list) arity -> f =
  fun acc arity ->
    begin match arity with
    | O    ->  List.rev acc
    | I a  ->  fun x -> f_aux (x :: acc) a
    end
let f arity = f_aux [] arity
# f (C(C(C O))) ;;
- : int -> int -> int -> int list = <fun>
# f (C(C(C O))) 111 222 333 ;;
- : int list = [111; 222; 333]

As is common with GADTs, type inference is not enough and you have to annotate your definition with the intended type, including an explicit universal quantification (type f. … where f is the type variable being quantified).

The GADT defined above can only describe variadic functions that deal with ints, but notice that you can easily extend it to allow more types of arguments (then of course, you should adapt your variadic functions so that they deal with these added possibilities):

type (_, 'r) arity =
  | O : ('r, 'r) arity
  | I : ('f, 'r) arity -> (int->'f, 'r) arity
  | B : ('f, 'r) arity -> (bool->'f, 'r) arity
  | C : ('f, 'r) arity -> (char->'f, 'r) arity
  | S : ('f, 'r) arity -> (string->'f, 'r) arity
  (* etc. *)

let rec g_aux : type f. string -> (f, string) arity -> f =
  fun acc arity ->
    begin match arity with
    | O    ->  acc
    | I a  ->  fun x -> g_aux (acc ^ string_of_int x) a
    | B a  ->  fun x -> g_aux (acc ^ if x then "true" else "false") a
    | C a  ->  fun x -> g_aux (acc ^ String.make 1 x) a
    | S a  ->  fun x -> g_aux (acc ^ x) a
    (* etc. *)
    end

let g arity = g_aux "" arity
# g (S(I(S(B(C O))))) ;;
- : string -> int -> string -> bool -> char -> string = <fun>
# g (S(I(S(B(C O))))) "Number " 42 " is prime. I swear, it’s " true '!' ;;
- : string = "Number 42 is prime. I swear, it’s true!"

As a matter of fact, this is essentially how pretty-printing is implemented in OCaml: when you write Printf.printf "%s%b" …, the format string is not actually a string, it is syntactic sugar kindly supplied by the compiler for a value of some very complicated GADT type such as (_,_,_,_,_,_) format6 (6 type parameters!). You might just as well build the GADT value by hand (don’t). This syntactic sugar is the only magic that the compiler does for pretty-printing, everything else works with standard language features.

Well, we have a system that works, at least it typechecks. Syntax is not pretty unless the compiler gives you sugar. More importantly, arities are encoded and checked within the static type system, which means, they are known at compile-time. You cannot (or at least it’s hard to do safely) read an arity as input of your program, dynamically, at run-time.

The actual question is: why would you actually need to do that, instead of just using a list? It brings nothing except syntactic convenience perhaps.

Upvotes: 4

Jeffrey Scofield
Jeffrey Scofield

Reputation: 66818

Your requirement doesn't make sense, since there is no way to dynamically change the number of parameters of a function at runtime. The number of parameters in any function call is directly visible by examining the text of the source code:

f a b    (* Two parameters *)
f a b c  (* Three parameters *)

There is no dynamic evaluation mechanism in OCaml (like the eval mechanism of other languages). This is part of what it means to be statically typed.

You can get the effect you want just by passing a list to the function.

Upvotes: 2

Related Questions