Jane Street Style Guide
Generalities
This guide documents what you could call Jane Street’shouse style.It’s not an absolute guide to how code is written everywhere here;different groups make different decisions in some cases. We’ll documentsome of the variations here, while noting which one of those we think ofas the house style.
Even though the house style isn’t universally followed, it’s a gooddefault, and it’s the style we use in our most foundational libraries,includingBase
,Core
,Async
andIncremental
. Indeed, thoselibraries are generally good places to look at for examples of goodpractice.
Formatting
Indentation should follow the rules of
ocp-indent
. This isenforced by default by Jenga, and is included in Jane Streetdefault vim and emacs configs, but you can also achieve it byusingocp-indent
with theJaneStreet
ruleset.Formatting should follow the rules of
ocamlformat
with theJaneStreet
profile.90 characters is the maximum line length.
Naming
Identifiers (constructors, variables, structures, type names, …)are written using underscores to separate words, not in
CamelCase
.So writenum_apples
notnumApples
andFoo_bar
, notFooBar
.Identifiers that exist for short scopes should be short, e.g.,
List.iter~f:(funx->x+1)numbers
whereas identifiers that live for large scopes should be larger andmore descriptive.
Boolean-returning functions should have predicates for names.For instance,
is_valid
is better thancheck_validity
.Use
unsafe
to indicate memory-unsafety only. e.g., thefollowing, fromBase.Array
, is an appropriate use of unsafe.(** Unsafe version of [set]. Can cause arbitrary behavior when used for an out-of-bounds array access. *)externalunsafe_set:'at->int->'a->unit="%array_unsafe_set"
This function, however, from
Base.Int_intf
, usesunchecked
instead of unsafe to indicate a function that may have badbehavior in certain circumstances, but is not memory unsafe.(** [of_float_unchecked] truncates the given floating point number to an integer, rounding towards zero. The result is unspecified if the argument is nan or falls outside the range of representable integers. *)valof_float_unchecked:float->t
Comments
Use OCamldoc style comments – starting with
(**
– in mli’s. Makesure to use square-brackets to enclose small OCaml values, and {[]} to enclose larger blocks.Avoid comments that add no useful information to the type andfunction name.i.e., avoid this:
(** Compares two strings *)valcompare:string->string->int
Whereas this would be better.
(** [compare x y] Compares two strings lexicographically *)valcompare:string->string->int
If you really can’t find anything useful to add with a comment, it’sacceptable to have a comment that is redundant with the type andname, particularly in broadly-aimed libraries like Base and Core.This reflects the fact that it’s considered gauche in some circlesto have functions with no documentation whatsoever. As an example,in the Bytes module, we might have this
(** [length t] returns the number of bytes in [t]. *)vallength:t->int
even though the content is largely duplicative.
Let-syntax
When programming with monadic and applicatives, the use oflet%bind
isgenerally preferred in cases where an explicit variable is bound. For example,prefer this:
let%bindx=fooyinlet%bindz=barxinreturnx+z
to
fooy>>=funx->barx>>=funz->returnx+z
That said, even when usinglet%bind
orlet%map
, infix operators arestill useful when operating in a point-free style, i.e., when notbinding variables. So, we might write.
let%bindx=m>>|Model.xinfoox
rather than
let%bindx=(let%mapm=minModel.xm)infoox
Opening Modules
Top-Level
Only open modules with a clear and standard interface, and open all suchmodules before defining anything else, e.g.,
openCoreopenAsyncletsnoo()=...
not:
letnow()=...(* shadowed below by opening Time *)openCoreopenTime
Local “open”
Even using a local “open” will pollute the namespace of an expressionand risk silently shadowing a variable of the same type. When you do usea local-open, you should aim to keep the scope small. This notation:
letfg=Time.(now()<lockout_time)
is generally preferable to this one:
letfg=letopenTimeinnow()<lockout_time
because the scope is more clearly delimited.
That said, when the interface being opened has a standard and widelyunderstood API, then thelet open
syntax is preferred.
letopenOption.Monad_infixinload_config()>>=create
Some modules provide anInfix
module or anO
module which isspecifically designed for local opens.
Signatures
Most modules should contain a single type named
t
. For instance,theString
modules definesString.t
. When you’re reading theString
interface and you seet
, think ‘string’ in your head.Prefer functions that return explicit options (or errors) overthrowing exceptions. If your function routinely raises anexception, put
_exn
in the name. For example:valcreate:string->toptionvalcreate_exn:string->t
Functions in a module
M
should typically takeM.t
as their firstargument.An exception to this is that optional arguments should be placedbefore the
M.t
argument if that is the sole positional argument,to allow the optional arguments to be erased.Prefer standard signature includes to hand-written interfaces.E.g., prefer this:
includeComparable.Swithtypet:=t
over this:
valt_of_sexp:Sexp.t->tvalsexp_of_t:t->Sexp.t
Most comments should be for users of a module, not implementers,which means that most comments should be in the
mli
. We placecomments above the function signature, module, or record fielddescribed. Small comments can also go to the right of a line ofcode.
Defensive Programming
Always annotate the type of ignored values. That way the compilerwill complain if the type changes. For example, imagine what happensto
ignore(Unix.wait`Any);
when
valwait:[`Any|`PidofPid.t]->Pid.t
changes to return the exit code instead of raising on non-zero:
valwait:[`Any|`PidofPid.t]->Pid.t*Exit.t
It’s better to write:
ignore(Unix.wait`Any:Pid.t);
The same logic applies to underscores, except where the type ismore-or-less pinned down anyway.
If a function takes two arguments of the same type and the argumentsare used differently, they should usually be labeled to avoidaccidental permutation. Notable exceptions include
List.append
and(-)
where the order is sufficiently clear. For example:valsend:source:User.t->dest:User.t->unit
Avoid catch-all cases in pattern matches. For example, prefer this:
letposition_change=function|Executione->Dir.sign(Execution.dire)*Execution.sizee|Ack_|Out_|Reject_->0
to this.
letposition_change=function|Executione->Dir.sign(Execution.dire)*Execution.sizee|_->0
Both are correct, but the former will produce an error if
Correction
is added to the type being matched on, and the latterwon’t.Optional arguments should typically only be used for functions thatare called in many different places. That’s because optionalarguments make your code less explicit, which makes the call sitesharder to understand, and it makes it easy to forget to specify theargument in a case where it’s required.
A good rule of thumb is to avoid optional arguments for functionsthat are not exposed in your module interface.
Directory Names
Use dashes (“-
”) in for multi-word directory names, instead ofunderscores (“_
”). Thus:
~/local/trunk/trader-tools/
instead of:
~/local/trunk/broken_directory_name/
Exceptions
It is OK for a function to raise an exception in an exceptionalcircumstance. We often, but not always, suffix such functions with_exn
. Although raising is fine, one should not write code that dependson which exception is raised. That is, one should never declareexceptions or match on them. Doing so is problematic in a large codebase because the type system doesn’t track which exception is raised. Ifcallers of a function need to discriminate among error cases, then thefunction should return a variant distinguishing the cases.
Instead of declaring new exceptions, one should use a function thatimplicitly constructs an exception, e.g.:
raise_s[%message"something bad happened"(t:t)]
Tests
Test your code. Some codebases do better with having unit tests forevery function, some are better with end-to-end tests, but generallyspeaking all significant changes should have tests demonstrating theireffect.
Tests can sometimes go in the same file, but typically should go in aseparate test library. This is preferable for a number of reasons: Forone thing, it encourages you to write tests against the exposed API ofyour code, which is usually the right approach. It also allows you todraw on more dependencies in your test code than you might want to linkin to your production code.
Expect tests are the preferred way of writing tests. This doesn’t meanthat you shouldn’t use Quickcheck or other forms of property tests; it’sjust thatlet%expect_test
is a single umbrella under which you canwrite all of these kinds of tests conveniently.
Private
submodules
A number of modules expose aPrivate
submodule. User code shouldnot use functions in aPrivate
submodule.Private
submodulescontain functionality that is internal to the implementation, intendedfor use in closely-related code like expects tests and benchmarks ofthe module itself. Such code is often in another library and needsaccess to private internals of the main module.