We describe guiding principles used in the code.
Lines with a function
or classdef
declaration must be standalone, i.e. must not end with a continuation ...
.
This mean that those lines can go over the column limit (TODO: add link to this limit when we decide on one.)
Whenever possible, we reuse primitive types and do not wrap them in classes.
To perform generic operations upon them, we pass around a “typeclass”, which is a collection of methods/default values for the type.
All typeclasses derive from Laws
which provides the test harness for property-based checks.
Permutations are 1 x n
vectors containing a permutation of the integers {1..n}
, stored as double coefficients.
Signed permutations are 1 x n
vectors containing the integers {+/-1..+/-n}
, such that abs(signedPerm)
is a permutation.
Some typeclasses define domains, which are mathematical sets.
Domains define the following methods:
replab.Domain.eqv
tests for equality,
replab.Domain.sample
provides a random element of the domain.
Example: replab.Permutations(10)
is the domain of permutations acting on 10 elements.
Other typeclasses define relations between domains, such as replab.Action
.
Typeclasses that depend on other types contain the typeclasses of those types as properties.
For example, a replab.Action
typeclass contain a property G
describing the group structure, and a property P
describing the domain of elements being acted upon.
There, G
and P
are domains, while Action
itself is not.
Typeclasses can define laws, which are defined as methods in a companion class deriving from replab.Laws
. These methods are of the form law_name_of_the_law_TYPES
, where TYPES
describes the parameters of the law (see replab.Laws
).
For each algebraic structure (monoid, group, …), we define:
An abstract base class (ex: Monoid
) with no constructor, where the abstract methods have
a body containing an error (see below).
A law checking class (ex: MonoidLaws
)
(optional) A generic implementation using function handles (ex: replab.lambda.Monoid
)
CamelCase/camelCase follows the separations of US English; words
separated either by a hyphen or a space are separated in the camel case.
Class names are UpperCamelCase
. Method and function names are
lowerCamelCase
; exception: functions that create objects such as DihedralGroup
.
If a computed property is cached, it is implemented using a protected property expensiveProp_
, and accessed using a method
expensiveProp
that then checks if expensiveProp_
has already been computed.
When there is only one implementation of an interface, the constructor is public and can be called directly.
Example: S4 = replab.Permutations(4)
When a base interface is available and several optimized implementations
are available, the object should be constructed by calling a function
with the same name as the base interface with a make
prefix, in lowerCamelCase
.
Example: c = replab.makeEquivariant(rep1, rep2)
That function will then construct the object with the best performance characteristics.
As Octave does not support the methods (Abstract)
syntax, we provide
generic implementations for abstract methods by having the method body be
a single line with error('Abstract');
.
In general, we avoid using methods/functions with a variable number of arguments, as it makes future extensions with optional parameters difficult. Exception: helper methods/function names with of
in their names.