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.)