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
end
Now, 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 Operator you
suddenly realize that other definitions probably also
belong to it.
You might have functions with names such as parse_operator or
action_of_operator:
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 -> (/)
end
Namespacing 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. ■