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.
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.
Use sparingly, and especially not for changing the order of execution. Use ⍨
or a variable instead. For example, instead of
r←(2↑a),b | R←(2↑⊣),⊢ |
write | |
r←b,⍨2↑a | R←⊢,⍨2↑⊣ |
or | |
head←2↑ar←head,b | Head←2↑⊣ R←Head,⊢ |
Acceptable uses of parentheses are:
⊢⍤/
pattern insteadFor 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 :ElseIf
s, then perform the main computation in the :Else
block.
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
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.
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
.
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.
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.
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.
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 |
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 |