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