When reading code written in OCaml or Standard ML, I keep seeing variant constructors having ad-hoc prefixes or suffixes used for namespacing.
Here’s an example from Modern Compiler Implementation in ML (page 98):
type operator = PlusOp | MinusOp | TimesOp | DivideOp
And here’s just one of many examples from Facebook pfff tool:
type hint_type = | Hint of name * type_args option | HintArray of tok | HintQuestion of tok * hint_type | HintTuple of hint_type comma_list paren
What you can do instead is to drop the prefix/suffix and use a small module as a namespace instead:
module Operator = struct type t = Plus | Minus | Times | Divide end
module Hint = struct type t = | Name of name * type_args option | Array of tok | Question of tok * t | Tuple of hint_type comma_list paren endNow, at the use site you can select the most readable option depending on the context. You can spell it all out if the variants are only used briefly:
let operators = [Operator.Plus; Operator.Minus]
Or you can create a module alias if you use the variants a lot:
module Op = Operator let operators = [Op.Plus; Op.Minus]
Or you can locally open the module if you need to use them intensely in a particular scope:
let operators = let open Operator in [Plus; Minus; Times; Divide] (* or *) let operators = Operator.([Plus; Minus; Times; Divide])
Or you can just open the module at the top of your file if that’s your thing:
open Operators let operators = [Plus; Minus; Times; Divide]
Now that you have a module like
suddenly realize that other definitions probably also
belong to it.
You might have functions with names such as
type operator = PlusOp | MinusOp | TimesOp | DivideOp let parse_operator = function | "+" -> Some PlusOp | "-" -> Some MinusOp | "*" -> Some TimesOp | "/" -> Some DivideOp | _ -> None let action_of_operator = function | PlusOp -> (+) | MinusOp -> (-) | TimesOp -> ( * ) | Divide -> (/)
Now you can group them all in the namespace module and give them more appropriate names (for module context):
module Operator = struct type t = Plus | Minus | Times | Divide let of_string = function | "+" -> Some Plus | "-" -> Some Minus | "*" -> Some Times | "/" -> Some Divide | _ -> None let to_action = function | Plus -> (+) | Minus -> (-) | Times -> ( * ) | Divide -> (/) endNamespacing functions this way has same benefits to namespacing your original type. The new flexibility affords you better readability or shorter expressions at the call site. For example, you can write something like this now:
let action = Operator.(source |> of_string |> to_action)
Another advantage is that such module visually groups related type and functions in your source file.
Yet another advantage: you can use your new module in functor context, assuming it implements the required signature:
module OperatorSet = Set.Make (Operator)
One of my fist complains coming from object-oriented to functional programming was that so much functional code looks like big bags of functions and types with little structure. Well, ML structures to the rescue!
Nowadays whenever I have a type and a bunch of related functions (sounds familiar?), I’m more inclined than not to group them in a namespace module. ■