Implicit arguments.

Alain Frisch

This is a follow-up on yesterday’s post about implicit values. With five more lines added to the branch, we know have implicit arguments as well.

The idea is simple: if a non-optional labeled argument, whose label starts with an underscore, is omitted at a given function call site, the compiler inserts automatically the magic underscore in place of this argument (if the same function call also has non-labeled arguments).

Here is an artificial example:

# let f ~_x () = _x + 2;;
val f : _x:int -> unit -> int = <fun>
# f ();;
Error: Cannot generate implicit value for type int
# let _x = 10 in f ();;
- : int = 12

Note that this change is not strictly compatible with the current behavior of OCaml. Normally, missing non-optional labeled arguments produce a partial application of the function. So f () would return a value of type _x:int -> int. I don’t think this is a big deal. At LexiFi, we have already switched to the semantics that missing non-optional labeled arguments produce an error (when the same function call has non-labeled arguments). This tends to produce better error messages, and we have piggy-backed on that to add some kinds of implicit arguments as well (for our dynamic types and other ad hoc features). In our experience, this change of semantics is not a problem at all. Moreover, in the implicits branch, the new behavior is only triggered when the label starts with an underscore.

Time to revisit the examples in yesterday’s post, with the new implicit argument patch. First, the printers:

module Printers : sig
  type 'a t
  val apply: _printer:'a t -> 'a -> string
  val make: ('a -> string) -> 'a t

  val _int: int t
  val _string: string t
  val _list: 'a t -> 'a list t
  val _tuple2: ('a t * 'b t) -> ('a * 'b) t
  val _tuple3: ('a t * 'b t * 'c t) -> ('a * 'b * 'c) t
end = struct
  type 'a t = ('a -> string)
  let apply ~_printer x = _printer x
  let make f = f

  let _int = string_of_int
  let _string = String.escaped
  let _list f l =
    Printf.sprintf "[%s]" (String.concat "; " (List.map f l))
  let _tuple2 (f1, f2) (x1, x2) =
    Printf.sprintf "(%s, %s)" (f1 x1) (f2 x2)
  let _tuple3 (f1, f2, f3) (x1, x2, x3) =
    Printf.sprintf "(%s, %s, %s)" (f1 x1) (f2 x2) (f3 x3)
end

type 'a my_type =
  | A of ('a * 'a) list
  | B of ('a * 'a) my_type

let rec _my_type: 'a. 'a Printers.t -> 'a my_type Printers.t =
  fun (type a) (_f : a Printers.t) ->
    (Printers.make
       (function
         | A x -> Printf.sprintf "A(%s)" Printers.(apply x)
         | B x -> Printf.sprintf "B(%s)" Printers.(apply x)
       ) : a my_type Printers.t)


let () =
  print_endline Printers.(apply [(A [(1,1)], "X"); (B (A []), "Y")])

And then, the type class example:

type 'a ops =
    {
     dump: ('a -> unit);
     succ: ('a -> 'a);
    }

let _ops_tuple2 (ops1, ops2) =
  {
   dump = (fun (x, y) -> ops1.dump x; ops2.dump y);
   succ = (fun (x, y) -> ops1.succ x, ops2.succ y);
  }

let _ops_int =
  {
   dump = (fun x -> print_endline (string_of_int x));
   succ;
  }


let dump ~_ops x = _ops.dump x
let succ ~_ops x = _ops.succ x

let f (type a) ~(_ops : a ops) (x : a) =
  dump (succ (succ x))

let () =
  f (3, 4)