Pure Functions
pure func marks deterministic code and makes programs easy to reason about.
`pure func`
A pure func always returns the same value for the same input. It cannot perform I/O, call impure functions, mutate global state, or accept impure callbacks. Restricted debug diagnostics are the deliberate exception below.
pure func add_one(n: Int) -> Int:
n + 1
Pragmatic Local Mutation
Local var bindings are allowed when the mutation cannot be observed outside the function.
pure func total(xs: List[Int]) -> Int:
var sum: Int = 0
for x in xs:
sum += x
sum
Keeping I/O at Program Boundaries
It's often useful to keep impure code limited to the "shell" of a program -- things like reading files, network calls, and printing. The core of your program can focus on pure data processing.
import:
system: read_file
pure func word_count(text: String) -> Int:
text
.words()
.length()
func main(args: List[String]) -> Int:
match read_file("notes.txt"):
Ok(text):
print(word_count(text))
0
Err(msg):
print_error(msg)
1
Larger Boundary Example
Rejected Effects
The same file read is rejected inside pure func; the boundary has to stay visible in the caller.
import:
system: read_file
pure func load_notes() -> Result[String, String]:
read_file("notes.txt") -- rejected
Pure Callbacks
Collection and parallel APIs can accept pure callbacks when they need deterministic work.
items.parallel(
-- `pure` can be omitted in closures where pure functions are required
func(chunk): chunk.map(func(x): x * 2)
)
Debug Diagnostics
A debug: block may use approved debug diagnostics, even inside a pure function. In normal builds the block is erased; debug and test builds retain it and can write to stderr without changing the return value. This is not permission to call arbitrary impure functions, read files, or use the network.
import:
debug
pure func score(value: Int) -> Int:
debug:
debug.log("score: ${value * 2}") -- stderr: score: 42 (debug/test only)
value * 2
func main(args: List[String]) -> Void:
print(score(21).to_string()) -- stdout: 42 (normal and debug builds)
Example
import:
system: read_file
record TextStats {lines: Int, words: Int, todos: Int}
pure func count_note_lines(text: String) -> Int:
var total: Int = 0
for line in text.lines():
if line.trim() != "":
total += 1
total
pure func count_todos(text: String) -> Int:
var total: Int = 0
for line in text.lines():
if line.contains("TODO"):
total += 1
total
pure func analyze(text: String) -> TextStats:
{
lines = count_note_lines(text),
words = text
.words()
.length(),
todos = count_todos(text)
}
pure func format_stats(stats: TextStats) -> String:
"lines: " + stats.lines.to_string() + ", words: " + stats.words.to_string() + ", todos: " + stats.todos.to_string()
func main(args: List[String]) -> Int:
path: String = "notes.txt"
match read_file(path):
Ok(text):
print(format_stats(analyze(text))) -- prints: lines: 3, words: 11, todos: 2
0
Err(msg):
print_error(msg)
1
Try it
blorp run analyze-notes.brp