APL style

This is my personal style guide for APL code, and is not to be taken as authoritative or representing the view of Dyalog or anyone else.

Table of Contents

Functions

General rules

Don’t be afraid of giving a function a good name even if only to use it once. Avoid diamonds and inline assignments unless necessary to show symmetries. Never use inline assignments that prevent re-executing the line if it errors when partially executed. Modified assignment is fine in moderation, and only within a visual range of the original assignment. Do as many test as necessary rather than relying on trapping errors. Prefer Rank () over bracket axis, and design your functions to be leading axis oriented. Bracket indexing is fine if would complicate the code. Localise relevant system variables rather than attempt to write code that can handle all combinations of ⎕IO, ⎕ML, ⎕WX, ⎕DIV etc.

Try to keep functions fitting on the screen. Break larger functions into sub-functions if necessary, but do not rely on semi-globals except for small inline dfns that loop to augment an accumulator. Structure your code in namespaces with a well-defined API.

For optimal performance, keep your arrays homogenous, flat, and with the last dimension used for data that will be read in sequence. However, this can impact readability, so reserve extremism to performance-critical code. Prefer mathematical approaches over loops. Stay with Boolean masks as far as possible. Use ]runtime -c "expr1" "expr2" to compare run times.

Parentheses

Use sparingly, and especially not for changing the order of execution. Use or a variable instead. For example, instead of

r←(2↑a),bR←(2↑⊣),⊢
write
r←b,⍨2↑aR←⊢,⍨2↑⊣
or
head←2↑a
r←head,b
Head←2↑⊣
R←Head,⊢

Acceptable uses of parentheses are:

  1. Short inline trains — long trains should be broken out and/or split up
  2. Application of derived functions with array operands
  3. Stranding including strand assignment — always parenthesise strand assignement!
  4. Forcing slashes to behave as functions Use the ⊢⍤/ pattern instead

Tradfns

For large "scripts" and structured programs. Use proper structures, not branching or early returns (→0 and :GoTo 0 and :Return). Rather, always use a full :If statement to skip to the end. Always spell out the full ending of control structures; :EndIf — not just :End.

Keep your code flat by testing and signalling error conditions first using :ElseIfs, then perform the main computation in the :Else block.

Dfns

For medium sized functions and utilities. Nested dfns are fine, but never use multi-line dfns inline. Rather, give them names and then apply them. Named dfns should be multi-line so they can be traced into, unless truly trivial.

Do not use a dfn instead of naming a variable. For example, instead of

r←{⍵/⍨10≤⍵}a,b

write

candidates←a,b
r←candidates/⍨10≤candidates

Tacit functions

Trains and derived functions can be used inline if they are not too complex. If they need internal parentheses for anything other than the reasons 2 and 3 above, they are too complex.

Identifiers

Use proper names for things, not abbreviations, unless common for the field in question. Name variables for what they signify, not how they are used. For example, call a mask of chess positions that are inside the board inside rather than valid. Name functions for what they do or compute rather than what they are used for. For example, name a function Attributes rather than ExtractAttributes and Compress rather than ApplyCompression.

Comments

All named functions should begin with a comment on their purpose. Larger functions should begin with a comment section describing argument(s) and result.

Introduce sections of larger functions with a comment describing the section’—s purpose. Also comment lines that are non-obvious, but tell what they do, not how they do it.

Structure

In tradfns, use :Section with a descriptor to delimit larger amounts of code dedicated a separate purpose. One line for every half-dozen or so should be left blank, preferrably preceded by a comment describing the purpose of the next few lines. If two lines have exactly the same structure, for example because two equivalent arguments need preprocessing before being merged, then put a blank line before and after those two to emphasise this.

Naming Conventions

If a function computes something, then it is likely that an appropriate name for the result will also be an appropriate name for the function (save for casing).

The below schemes allow the reader to distinguish syntactic classes, thereby enabling static analysis of the code. Note that the naming schemes distinguish the role of identifiers, not the name class (⎕NC). This means that niladic functions are named like variables because they play the syntactic role of arrays.

Camel Case

arrays use lower camel case (a.k.a. dromedary case)  name  multiWord labels, constants, variables, fields, properties, niladic functions
refs use lower camel case with underscore suffix  name_  multiWord_ scalar refs (but not objects themselves), arrays of refs
functions use upper camel case (a.k.a. Pascal case)  Name  MultiWord tradfns, dfns, derived functions, trains
modifiers use upper camel case with underscore prefix _Name _MultiWord monadic operators, dyadic operators with curried right operands
combinators use upper camel case with underscore omnifix _Name_ _MultiWord_ dyadic operators

Snake Case

arrays use lower snake case  name  multi_word labels, constants, variables, fields, properties, niladic functions
refs use lower snake case with underscore suffix  name_  multi_word_ scalar refs (but not objects themselves), arrays of refs
functions use upper snake case  Name  Multi_word tradfns, dfns, derived functions, trains
modifiers use upper snake case with underscore prefix _Name _Multi_word monadic operators, dyadic operators with curryied right operands
combinators use upper snake case with underscore omnifix _Name_ _Multi_word_ dyadic operators