Namespacing Variants in ML

2015-04-12

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

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]

Modular Programming

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

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)

Conclusion

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

Comment on Reddit

Comment on Hacker News

Follow me on Twitter