-
Notifications
You must be signed in to change notification settings - Fork 12
ATK Code Style Guidelines
An overview of rules adopted for the ATK library in SC3
Created by gh-md-toc
- The SuperCollider Style Guidelines are followed.
- The ATK generally prefers Receiver-verbose.
- Except for flow control, for which Functional-verbose style is preferred.
- Explicit expression is preferred over terse "Syntax Shortcut" code.
- Deviations from the guidelines should be minimized, though local context may dictate that another style is decidedly more legible. For example one-line flow control.
- It is recommended that developers avoid Inline Compiler Warnings.
- Set
LanguageConfig.postInlineWarnings_(true)
or tick Interpreter Options "Post Inline Warnings"
- Set
Styles can be broken down into two differentiating characteristics:
- Receiver Notation (Generally preferred)
3.series(4, 10)
- Functional Notation (Preferred for flow control)
series(3, 4, 10)
- Verbose (Generally preferred)
if(k.isNumber, { // includes parenthesis, compact, no space after 'if'
"This is true!".postln
}, { // comma-separated cases
"This is false!".postln
});
- Sparse ("modern", not preferred)
if (k.isNumber) { // spaces separate statements
"This is true!".postln
} {
"This is false!".postln
};
- Tabs are used, not spaces. Tab length is set to 4 spaces.
- Spaces should surround operators, including comparison operators.
if(a != 45, { a = a + 6 })
-
Exceptions include fractions and common quantities:
-
1/2
,pi/2
,(3/2).sqrt
,3.sqrt/2
, etc.
-
- Spaces always follow commas.
[1, 3, 9]
- Spaces are used to pad curly brackets, but not parentheses or square brackets.
fork({ 2.wait; "ok go!".postln })
[1, 3, 9]
- Use K&R style for multi-line functions:
if(v.isKindOf(Array), {
k = 4 + 4;
wrAttArr.(k, v)
}, {
k = 8 + 4;
wrAtt.(k, v)
});
- A newline is place at the end of every file.
- Use
|pipes|
instead of thearg
keyword. -
var
keyword is always under first function line.- An empty line following the var declarations can increase readability.
- For very short (e.g. 2-line) methods, omitting the space can be ok.
// multi-line methods: space separating variable from statements preferred
magMagI {
var wp = this.wp;
var wu = this.wu;
^(wp * wu).sqrt
}
magI {
var i = this.intensity;
^Complex.new(
i.real.squared.sum.sqrt,
i.imag.squared.sum.sqrt
)
}
// 2-line methods: omitting space separating variable from statements ok
degree { |degree|
var res = this[degree.asHoaDegree.indices];
^(res.size == 1).if({ res = res[0] }, { res });
}
- Use preceding backslash notation rather than single quotes to declare and reference. E.g.,
\mySymbol
is preferred to'mySymbol'
.
-
&&
and||
are preferred over keywordsand:
andor:
. - Multiple tests should be enclosed in their own parentheses.
if((test > 4) && (test2.isNil), {
"yes".postln
}, {
"no".postln
});
- Exception: when efficiency is a concern, the comparison keywords should be used, with the second test enclosed in curly braces.
if((test1 > 4) and: { test2.isNil }, {
"yes".postln
}, {
"no".postln
});
Within an instance method, use the keyword this
to explicitly refer to instance variables with getters.
MyClass {
var <instVar;
instanceMethod { |arg|
^(this.instVar + arg)
}
}
- Use
theta
andphi
for azimuth and elevation angles, andradius
for radial arguments.
HoaEncodeDirection.ar(in, theta, phi, radius, order)
- For a transform with a single angular argument, use
angle
.
HoaRotate.ar(in, angle, order)
- For a transform with multiple angular arguments, use names indicating roles.
HoaYPR.ar(in, yaw, pitch, roll, order)
HoaNFCtrl.ar(in, encRadius, decRadius, order)
-
Object.new()
is preferred toObject()
. Though in areas of dense object instantiation,Object()
may be preferred (e.g. UI elements).
-
array[i]
is preferred toarray.at(i)
.
- Align nearby comments and offset from the code.
^(beamShape == nil).if({
instance.initBasic // (\basic, \amp)
}, {
instance.initBeam(beamShape, nil) // (beamShape, \amp)
})
- Avoid chaining too many methods together or performing calculations within the argument list of method calls. Use well-named temporary variables to carry your intermediate values along so it's clear what produces your returned values.
// Too crowded!
set { ^(this.class.asString.keep(3).toUpper ++ this.order.asString).asSymbol }
// longer but clearer
set {
var classStr = this.class.asString.keep(3).toUpper;
var orderStr = this.order.asString;
^(classStr ++ orderStr).asSymbol
}
Functional-verbose style is preferred for flow control. Flow control intervenes in the sequential execution of code—where execution might be forked, skipped, looped, etc. Some examples include if
, switch
, case
, while
, fork
, defer
, for
, forBy
, block
.
Notable exceptions include do
and collect
, for which Receiver-verbose style is preferred.
For if
, while
, switch
and case
expressions, avoid variable declarations within flow control functions. Doing so allows inline optimization and prevents Inline Compiler Warnings. Please review the discussion here. To maintain clarity, comment variable declarations made outside individual use case functions as facilitating inline optimization.
// if - true only
if(k.isNumber, {
"okok".postln;
k = k * 35
});
// if - true and false cases
if(k.isNumber, {
"oh yes".postln;
k = k - 35
}, {
"oh no".postln;
k = nil
});
// very short
if(k.isNumber, { "okok".postln });
// very short, with else
x = if(k.isNumber, { k }, { nil });
// receiver notation (exceptional)
// enclose test in parentheses
x = (k.isNumber).if({ k }, { nil });
result = switch(a,
\one, { 1 }, // one-line function stays on same line [UND]
\two, { 2 },
\three, { // multi-line function
"picked three - functional style".postln;
3
},
{ "default" } // default case on it's own line
);
// switch - very short
x = a.switch(13, { 1 }, 24, { -1 });
result = case(
{ a == \one }, { 1 }, // one-line function stays on same line
{ a == \two }, {
"picked two".postln;
2
},
{ a == \three }, { 3 },
{ "default" }
);
(
var i = 0;
while({ i < 12 }, { // start function on same line (coincides with defer/fork above)
i.postln;
i = i + 1;
});
"done".postln;
)
// multi-line
(
12.do({ |elem, i|
var myVar;
myVar = elem.squared;
elem.postln;
myVar.postln;
})
)
// short, single line
squares = 12.collect({ |elem| elem.squared });
// multi-line, no second arg specified
fork({
callMyAsyncProcess();
"giving the process some breathing room".postln;
1.5.wait;
okKeepMoving();
});
// multi-line, no second arg specified
// may be clearer in some cases
defer(
{ // ... to start function on following line
a = 45 / 23;
"okok".postln;
myButton.state = 2;
}
);
// multi-line second arg specified
fork({ // start function on same line
a = 45 / 23;
"okok".postln;
35
},
AppClock // second arg on own line
);
// multi-line second arg specified
// may be clearer in some cases...
fork(
{ // ... to start function on following line
a = 45 / 23;
"okok".postln;
},
AppClock
);
// a short one-liner
defer({ mySlider.value_(0.7) }, 2);
// exception: receiver notation ok on one line
{ mySlider.value_(0.7) }.defer(2);
- Prefer to use soft wrapping in the IDE, rather than hard wrapping (manual newlines).
This allows easier future edits.
However, also avoid long paragraphs. This makes for more natural linebreaks in the
.schelp
files, as well as for more readable rendered docs. - No trailing whitespace (IDEs like Atom can be configured to remove it).