Skip to content

ATK Code Style Guidelines

Michael McCrea edited this page Nov 7, 2022 · 18 revisions

An overview of rules adopted for the ATK library in SC3

Created by gh-md-toc

Guidelines Overview

  • The SuperCollider Style Guidelines are followed.
  • The ATK generally prefers Receiver-verbose.
  • Except for flow control, for which Functional-verbose style is preferred.
    • Notable exceptions are do and collect, for which Receiver-verbose is also 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"

Style Hierarchy

Styles can be broken down into two differentiating characteristics:

1) Notation

  • Receiver Notation (Generally preferred)
3.series(4, 10)
  • Functional Notation (Preferred for flow control)
series(3, 4, 10)

2) Verbosity

  • 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
};

Syntax Guidelines

Whitespace, operators, parentheses

  • 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.

arg, var keywords

  • Use |pipes| instead of the arg 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 });
	}

Symbols

  • Use preceding backslash notation rather than single quotes to declare and reference. E.g., \mySymbol is preferred to 'mySymbol'.

multi-test comparisons

  • && and || are preferred over keywords and: and or:.
  • 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
});

Instance methods in the local context

Within an instance method, use the keyword this to explicitly refer to instance variables with getters.

MyClass {
    var <instVar;

    instanceMethod { |arg|
        ^(this.instVar + arg) 
    }
}

Coordinate and transform arguments

  • Use theta and phi for azimuth and elevation angles, and radius 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)

.new()

  • Object.new() is preferred to Object(). Though in areas of dense object instantiation, Object() may be preferred (e.g. UI elements).

Element Access

  • array[i] is preferred to array.at(i).

Comment spacing

  • Align nearby comments and offset from the code.
^(beamShape == nil).if({
	instance.initBasic			// (\basic, \amp)
}, {
	instance.initBeam(beamShape, nil)	// (beamShape, \amp)
})

Avoid extraneous compound operations

  • 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
}

Flow control

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

// 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 });

switch

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 });

case

result = case(
	{ a == \one }, { 1 },		// one-line function stays on same line
	{ a == \two }, {
		"picked two".postln;
		2
	},
	{ a == \three }, { 3 },
	{ "default" }
);

while

(
var i = 0;
while({ i < 12 }, {  // start function on same line (coincides with defer/fork above)
	i.postln;
	i = i + 1;
});
"done".postln;
)

do, collect

// multi-line
(
12.do({ |elem, i|
	var myVar;

	myVar = elem.squared;
	elem.postln;
	myVar.postln;
})
)

// short, single line
squares = 12.collect({ |elem| elem.squared });

fork, defer

// 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);

Docs (.schelp)

Line endings

  • 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).