-
Notifications
You must be signed in to change notification settings - Fork 52
Code Style Guide
Cyrus Omar edited this page Jul 17, 2024
·
14 revisions
- Don't leave any warnings. If necessary, you can suppress a warning by putting the
[@warning "-#"]
annotation narrowly on the node generating the warning, where#
is the warning code. Leave an explanation for why that annotation is necessary when you do so.
- Only use the anonymous argument form (
fun | pat => exp ...
) when there is exactly one input to the function. Otherwise, name all of the arguments and thenswitch
on the intended argument, even if it happens to be the last one in the argument list.
- Avoid using functions that throw a generic exception like
Not_found
. Often, there is an alternative version of the function that returns an option type or default value (where you can even raise a more searchable exception usingfailwith
).
- Each type should have its own module, even if it is small.
- Whenever possible, place
[@deriving (show({with_path: false}), sexp, yojson]
on type definitions, which will automatically derive serializers. This will generally require anopen Sexplib.Std
at the top of your module to open the standard sexp serializers for base types likestring
andint
.
- Each module should have its own
.re
file unless there is a good reason not to do that. (It simply being small is not a good reason.) - Use
open
sparingly, and as narrowly as possible. Prefer using type annotations so that type inference can discover which constructor you mean from context.
- Make sure you are using the preferred variable prefix for the type of the variable, if it is listed on the type definition (see above).
- Prefer numeric suffixes for different values of the same type in the same scope, e.g.
e1
,e2
,e3
, rather thane
,e'
,e''
. - When working with something that has functional state, use the same variable name every time the state updates. For example, use
u_gen
for allMetaVarGen.t
values rather thanu_gen
,u_gen'
, etc. This ensures that you do not accidentally use a stale state.
- Don't put
_
wildcards at the end ofswitch
expressions. This makes it difficult for the exhaustiveness checker to help us see where we need to add new cases when we add a new constructor to the datatype. Use multiple partial matches, like(Sum(_, _), _)
instead.
- If you find yourself "failing in an obvious manner" with nested switch statements, you should prefer expressing this more concisely with let-syntax (typically, this is defined when the underlying module is a Monad, but you don't need to "understand" Monads to use let-syntax).
Example:
switch (maybe_something) {
| None => None
| Some(x) => switch (maybe_y(x)) {
| None => None
| Some(y) => Some(y + 5)
}
}
=>
open OptUtil.Syntax; // here we define a Monad instance for option
let* x = maybe_something;
let+ y = maybe_y(x);
y + 5
These two snippets of code are equivalent.
The let*
desugars into a call to the bind
(semantically, flatMap
) function. let+
desugars into a map
function. This means the above code desugars into:
OptUtil.bind(maybe_something, x => {
OptUtil.map(maybe_y(x), y => y + 5)
})