Suppose I have a function which has, amongst its arguments, an optional flag that mildly changes its behaviour if present: something like
val of_int : ?atomic:bool -> int -> Initialiser.t
(** [of_int ?atomic value] creates a C initialiser with an integer type and value;
if ?atomic is present and true, the type will be `atomic_int`, else `int`. *)
Usually, if I’m making these flags, I’ll encode them just as above: as optional Booleans with a default value false. This makes it easy to delegate the decision as to which value to pass in further up the call chain, or to run-time, but gives no hints in the type system as to whether the default is true or false.
I saw, in some Jane Street codebase I think, a different approach:
val of_int : ?atomic:unit -> int -> Initialiser.t
(** [of_int ?atomic value] creates a C initialiser with an integer type and value;
if ?atomic is present, the type will be `atomic_int`, else `int`. *)
This seems a bit clearer: since there’s only one value atomic can be if present, it stands to reason that its presence must mean true and its absence must mean false. However, we’ve lost the ability to just pass in ~atomic:some_other_boolean - we’d have to do ?atomic:(Option.some_if some_other_boolean ()), which is quite clunky.
Are either of these particularly more idiomatic/encouraged in OCaml?
(Of course, the winning move here might be to split function in two:
val of_int : int -> Initialiser.t
val of_atomic_int : int -> Initialiser.t
This feels a bit awkward, as the underlying records that this function is a wrapper over do treat atomicity as a boolean, and so we’re turning a boolean into a pair of functions and then, if we need to make a runtime choice between the two, turning it back into a boolean. Hmm.)