Language Reference: Best Practices
Guidelines for writing clear and concise Pointless code
These best-practices are meant to help you make effective use of the language features of Pointless. You can find detailed descriptions of the language features covered here in the language reference.
Note that this guide focuses on recommendations relating to the core language, rather than the standard library.
- Use Compound Assignment
- Separate Statements
- Avoid Getter Functions
- Avoid Setter Functions
- Use Objects for Records
- Don't Concatenate to Update
- Omit String Key Quotes
- Use Identifier Keys
- Use Key Punning
- Use Dot Syntax
- Don't Use Lists as Tables
- Use Row Lookups
- Use Column Operations
- Avoid Zero Length Check
- Use Push for Single Items
- Use Negative Indices
- Use Star to Repeat Values
- Use String Interpolation
- Omit Interpolation Parens
- Use Raw Strings
- Minimize Raw String Escapes
- Use Multi-Line Strings
- Leverage String Alignment
- Avoid Redundant Booleans
- Use Not
- Use Boolean Parentheses
- Use Set Membership
- Use Match
- Avoid Nested Conditionals
- Refactor Conditional Defs
- Use Pipelines
- Use Map and Filter Operators
- Omit Pipe Parentheses
- Avoid Anonymous Functions
- Use Arg in Pipelines
- Omit Initial Arg
- Put Primary Parameter First
- Write Pure Functions
- Avoid Unnecessary Returns
- Avoid Early Returns
- Avoid Else After Return
- Use Specific Functions
- Omit Module Prefixes
- Don't Shadow Globals
- Use Camel Case
- Use Leading Zero for Decimals
- Use Trailing Commas
- No Import Side Effects
- Export Objects
- Use Fixed-Path Imports
- Use Anonymous Loops
- Use Loop Index Variables
- Use Loop Value Variables
Use Compound Assignment
Use compound assignment operators when possible.
score += 1
price |= round
score = score + 1
price = round(price)
Separate Statements
Put statements on separate lines.
x = 6
y = 7
print(x * y)
x = 6; y = 7; print(x * y)
Avoid Getter Functions
Use built-in syntax to access list elements, object values, and table rows and columns.
games[0].score
Obj.get(Table.get(games, 0), "score")
Avoid Setter Functions
Use structural updates instead of setter functions to transform data structures.
evilTwin = player
evilTwin.malice = 100
player.enemies += 1
evilTwin = Obj.set(player, "malice", 100)
player = Obj.set(player, "enemies", player.enemies + 1)
Use Objects for Records
Use objects to represent records (structures with a fixed number of entries that each have a distinct role). Do not use lists as records.
point = { x: 1, y: 2 }
card = { value: 10, suit: "hearts" }
point = [1, 2]
card = [10, "hearts"]
Don't Concatenate to Update
Use variable updates instead of object concatenation to update record objects.
player.health += 1
player += { health: player.health + 1 }
Omit String Key Quotes
Omit quotes for keys in record objects.
{ city: "Chicago", state: "IL", population: 2721308 }
{ "city": "Chicago", "state": "IL", "population": 2721308 }
Use Identifier Keys
Use valid identifiers as record object keys and table columns.
{ userName: "Clementine", userId: 0 }
Table.of([
{ userName: "Clementine", userId: 0 },
{ userName: "Ducky", userId: 1 },
])
{ "user name": "Clementine", "user-id": 0 }
Table.of([
{ "user name": "Clementine", "user-id": 0 },
{ "user name": "Ducky", "user-id": 1 },
])
Use Key Punning
Use object key punning when setting an object key to a variable of the same name.
point = { x, y }
point = { x: x, y: y }
Use Dot Syntax
Use . syntax to access and update object keys and table
columns when possible.
city.name
city["name"]
Don't Use Lists as Tables
Use tables to store lists of record objects with matching keys.
Table.of([
{ city: "New York", state: "NY", population: 8478072 },
{ city: "Los Angeles", state: "CA", population: 3878704 },
{ city: "Chicago", state: "IL", population: 2721308 },
{ city: "Houston", state: "TX", population: 2390125 },
])
[
{ city: "New York", state: "NY", population: 8478072 },
{ city: "Los Angeles", state: "CA", population: 3878704 },
{ city: "Chicago", state: "IL", population: 2721308 },
{ city: "Houston", state: "TX", population: 2390125 },
]
Use Row Lookups
Use the row lookup feature of tables to find individual rows based on unique field values.
philly = cities[{ name: "Philadelphia" }]
philly = (cities ? arg.name == "Philadelphia")[0]
Use Column Operations
When possible, structure table operations around columns rather than rows.
products.price $= arg - 1
products $= arg + { price: arg.price - 1 }
Avoid Zero Length Check
Use isEmpty instead of len(...) == 0 to
check for empty data structures.
isEmpty(playlist)
len(playlist) == 0
Use Push for Single Items
Use push to append a single item to a list or table
instead of concatenation.
numbers |= push(n)
numbers += [n]
Use Negative Indices
Use negative indices to access items from the end of a list or table.
items[-1]
items[len(items) - 1]
Use Star to Repeat Values
Use the * operator to repeat strings and list elements.
"la" * 5
[0] * 10
"lalalalala"
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Use String Interpolation
Use string interpolation instead of concatenation.
"Hello $person.name!"
"Hello " + person.name + "!"
Omit Interpolation Parens
Omit unnecessary parentheses around interpolated variables.
"$price dollars for a pair of socks!?"
"$(price) dollars for a pair of socks!?"
Use Raw Strings
Use raw strings to avoid excessive escape sequences.
r"C:\Users\Ducky\Documents"
"C:\\Users\\Ducky\\Documents"
Minimize Raw String Escapes
Omit unnecessary levels of raw string quote escaping.
r#"Hello "world!""#
r##"Hello "world!""##
Use Multi-Line Strings
Use multi-line strings when they make string contents clearer.
"
▓▓ ▓▓▓ ▓ ▓▓
▓ ▓ ▓ ▓ ▓
▓▓ ▓ ▓ ▓
▓ ▓ ▓ ▓
▓ ▓ ▓▓▓ ▓▓
"
"▓▓ ▓▓▓ ▓ ▓▓\n▓ ▓ ▓ ▓ ▓\n▓▓ ▓ ▓ ▓\n▓ ▓ ▓ ▓\n▓ ▓ ▓▓▓ ▓▓"
Leverage String Alignment
Leverage the automatic trimming and alignment behavior of multi-line strings to format code more legibly.
if printPtls then
print("
▓▓ ▓▓▓ ▓ ▓▓
▓ ▓ ▓ ▓ ▓
▓▓ ▓ ▓ ▓
▓ ▓ ▓ ▓
▓ ▓ ▓▓▓ ▓▓
")
end
if printPtls then
print("▓▓ ▓▓▓ ▓ ▓▓
▓ ▓ ▓ ▓ ▓
▓▓ ▓ ▓ ▓
▓ ▓ ▓ ▓
▓ ▓ ▓▓▓ ▓▓")
end
These two strings produce the same output. For details see multi-line strings.
Avoid Redundant Booleans
Don't include redundant boolean comparisons in logical expressions.
if item.onSale then
print(item.price / 2)
end
if item.onSale == true then
print(item.price / 2)
end
Use Not
Use not instead of == false.
if not Math.isInt(quantity) then
print("quantity must be a whole number")
end
if Math.isInt(quantity) == false then
print("quantity must be a whole number")
end
Use Boolean Parentheses
Use parentheses in expressions that mix different boolean operators
(and, or, or not).
isGood and (isFast or isCheap)
isGood and isFast or isCheap
Use Set Membership
Use sets instead of lists to store a large number of values on which
you will be calling has.
scrabbleWords = Set.of(import "lines:scrabble-dict.txt")
has(scrabbleWords, "yeet")
scrabbleWords = import "lines:scrabble-dict.txt"
has(scrabbleWords, "yeet")
Use Match
Use match instead of if when matching an
expression with three or more possible values.
match spin
case "gimel" then
score += pot
pot = 0
case "hei" then
score += pot / 2
pot /= 2
case "shin" then
score -= 1
pot += 1
end
if spin == "gimel" then
score += pot
pot = 0
elif spin == "hei" then
score += pot / 2
pot /= 2
elif spin == "shin" then
score -= 1
pot += 1
end
Avoid Nested Conditionals
Flatten nested conditionals when possible.
if n > 0 then
"positive"
elif n < 0 then
"negative"
else
"zero"
end
if n > 0 then
"positive"
else
if n < 0 then
"negative"
else
"zero"
end
end
Refactor Conditional Defs
Lift variable assignments outside of simple conditionals.
sweetener =
if diet == "vegan" then
"agave"
else
"honey"
end
if diet == "vegan" then
sweetener = "agave"
else
sweetener = "honey"
end
Use Pipelines
Refactor nested function calls into pipeline syntax when possible.
games
| Table.summarize("team", getStats)
| sortDescBy("winPct")
| print
print(sortDescBy(Table.summarize(games, "team", getStats), "winPct"))
Use Map and Filter Operators
Use the map $ and filter ? operators instead
of map and filter functions.
numbers ? Math.isEven $ arg / 2
List.map(List.filter(numbers, Math.isEven), fn(n) n * 2 end)
Omit Pipe Parentheses
Omit parentheses for single-argument functions in pipelines.
numbers
$ Math.sqrt
| print
numbers
$ Math.sqrt()
| print()
Avoid Anonymous Functions
Use top-level function definitions instead of anonymous functions.
fn distance(point, center)
dx = point.x - center.x
dy = point.y - center.y
Math.sqrt(dx ** 2 + dx ** 2)
end
fn averageDistance(points, center)
points
$ distance(center)
| List.average
end
fn averageDistance(points, center)
distance = fn(point)
dx = point.x - center.x
dy = point.y - center.y
Math.sqrt(dx ** 2 + dx ** 2)
end
points
$ distance
| List.average
end
Use Arg in Pipelines
Use arg in function pipelines instead of anonymous
functions.
words $ translations[arg]
words $ fn(word) translations[word] end
Omit Initial Arg
Omit arg that would otherwise be the first argument in a
pipeline function call.
numbers $ Math.roundTo(2)
numbers $ Math.roundTo(arg, 2)
Note that arg cannot be omitted as the first argument
if subsequent arguments to the function also use arg,
since using arg stops the first argument to a pipeline
call from being implicitly passed. Refactor calls like these into
new functions to avoid this situation.
-- Best option: refactor into a new function
fn joinFirst(strs)
join(strs, strs[0])
end
strings | joinFirst
-- Incorrect: wrong number of arguments, since using `arg` in the second
-- argument means that the first argument is no longer implicitly passed
strings | join(arg[0])
-- Correct, but confusing
strings | join(arg, arg[0])
Put Primary Parameter First
Put the most important parameter first in a function definition. If a function could be described as transforming, accessing, or analyzing one of its arguments, then that argument is probably the most important.
fn swap(list, indexA, indexB)
-- Transform `list` by swapping the values at `indexA` and `indexB`
end
fn startsWith(string, prefix)
-- Check if `string` starts with `prefix`
end
fn swap(indexA, indexB, list)
-- Transform `list` by swapping the values at `indexA` and `indexB`
end
fn startsWith(prefix, string)
-- Check if `string` starts with `prefix`
end
Write Pure Functions
Write pure (side effect free) functions when possible.
fn showTask(task)
if task.done then
"[x] $task.name"
else
"[ ] $task.name"
end
end
tasks
$ showTask
$ print
fn printTask(task)
if task.done then
print("[x] $task.name")
else
print("[ ] $task.name")
end
end
tasks $ printTask
Avoid Unnecessary Returns
Don't use return for the final expression in a
function.
fn sqrt(n)
n ** 0.5
end
fn min(a, b)
if a < b then a else b end
end
fn sqrt(n)
return n ** 0.5
end
fn min(a, b)
if a < b then
return a
else
return b
end
end
Avoid Early Returns
Don't use early return statements that don't
simplify your code.
fn min(a, b)
if a < b then a else b end
end
fn min(a, b)
if a < b then
return a
end
b
end
Avoid Else After Return
Refactor code to avoid using elif or
else after a return statement.
fn findOdd(numbers)
for n in numbers do
if Math.isOdd(n) then
return n
end
print("Skipped $n")
end
end
fn findOdd(numbers)
for n in numbers do
if Math.isOdd(n) then
return n
else
print("Skipped $n")
end
end
end
Use Specific Functions
If a function exists that specifically accomplishes a desired task, choose it over more general functions.
chars("Hello world!")
pop(items)
split("Hello world!", "")
dropLast(items, 1)
Omit Module Prefixes
Omit module prefixes when calling global functions.
print("Hello world!")
Console.print("Hello world!")
Don't Shadow Globals
Don't shadow global built-in functions.
message = "Enter a rating 1-5: "
maximum = 5
prompt = "Enter a rating 1-5: "
max = 5
Use Camel Case
Use camel case for multi-word variable, function, table column, and record object key names. Don't capitalize single-word names. Don't capitalize the names of constants.
gameState = "paused"
fn point(x, y)
{ x, y }
end
pi = 3.141592654
gamestate = "paused"
fn Point(x, y)
{ x, y }
end
PI = 3.141592654
(Yes, the standard library module variables are capitalized; do as I say, not as I do)
Use Leading Zero for Decimals
Include a leading 0 in decimal literals for numbers
between 0 and 1.
n = 0.1
n = .1
Use Trailing Commas
When splitting a piece of comma-separated code across multiple lines, include a trailing comma on the last line before the closing delimeter.
days = [
"Lunes",
"Martes",
"Miércoles",
"Jueves",
"Viernes",
"Sábado",
"Domingo",
]
days = [
"Lunes",
"Martes",
"Miércoles",
"Jueves",
"Viernes",
"Sábado",
"Domingo"
]
No Import Side Effects
Library code should never have side effects when imported.
fn maximum(a, b)
if a > b then a else b end
end
fn showInfo()
print("Maximum™ Version 1.0, patent pending")
end
{ maximum, showInfo }
fn maximum(a, b)
if a > b then a else b end
end
print("Maximum™ Version 1.0, patent pending")
{ maximum }
Export Objects
Export objects instead of individual definitions.
fn maximum(a, b)
if a > b then a else b end
end
{ maximum }
fn maximum(a, b)
if a > b then a else b end
end
maximum
Use Fixed-Path Imports
Use import with the appropriate
specifier to load files
from a fixed path relative to your source file.
story = import "text:alice.txt"
-- Won't work if script is called outside the source directory
story = Fs.read("alice.txt")
Use Anonymous Loops
Omit unnecessary loop variables.
for 10 do
print("loading")
end
for n in range(10) do
print("loading")
end
Use Loop Index Variables
Use a second loop variable to track indices when looping over lists or tables.
for player, index in rankings do
print("$index $player")
end
index = 0
for player in rankings do
print("$index $player")
index += 1
end
Use Loop Value Variables
Use a second loop variable to track values when looping over objects.
for item, quantity in inventory do
print("$item x $quantity")
end
for item in inventory do
quantity = inventory[item]
print("$item x $quantity")
end