Effects (pure, mut, proc, action)
Acton tracks effects as part of function and callable typing. An effect marker tells you what the callable is allowed to do, so it is part of the type information you read and design with.
The four effect markers are:
pure: no side effectsmut: may update stateproc: functions that call actorsaction: action or callback style code used heavily in actor APIs
pure def square(x: int) -> int:
return x * x
class Counter:
value: int
def __init__(self):
self.value = 0
mut def next(self) -> int:
self.value += 1
return self.value
actor Greeter():
def hello(msg):
print(msg)
proc def show_square(g: Greeter, x: int) -> None:
g.hello("square: " + str(square(x)))
actor main(env):
counter = Counter()
greeter = Greeter()
print("counter:", counter.next())
n = square(7)
print("n:", n)
show_square(greeter, 9)
action def stop() -> None:
env.exit(0)
after 0.1: stop()
In this example:
squareispureCounter.nextismutshow_squareisprocbecause it calls an actorstopis anaction- the effect markers are part of the callable signatures, not notes
Effect inference
If you omit the effect marker, Acton infers it from the body.
Use explicit annotations when you want an API to promise purity, make mutation clear, or document that a callback or actor-facing entrypoint has a particular effect. The effect is part of the contract just like its argument and return types.
A useful first habit is to keep calculations pure and push printing,
I/O, and actor orchestration into a smaller layer of effectful code.
Read pure as "calculation only", mut as "may
update state", proc as "calls actors", and
action as "actor action or callback".
That division makes code easier to reason about. If a helper is pure, you know it only depends on its inputs. If it is mut or proc, you know it may change state or interact with actors, which makes its API more specific.
When the four effects show up
pureis common for ordinary calculations and helpers that should be easy to reuse anywheremutis common on methods that update state or work with local mutable dataprocis common for functions that orchestrate work by calling actorsactionoften appears in actor methods, timers, cleanup hooks, and callback types such asaction(str) -> None
Effects often explain why a signature looks the way it does. A function may have a simple data type and still be effectful, and the effect marker is what tells you whether it stays in pure computation or crosses into stateful or actor-driven work.
Effects also appear in inferred signatures. As your code gets more generic or callback-heavy, those effect annotations become part of how you read and design APIs. In Acton, purity is a real constraint on what a function may call, so effect annotations are part of the contract, not just commentary.
Practical guidance
- Prefer
purefor deterministic, test-friendly core logic. - Use
mutwhen a callable really updates state. - Use
procfor functions that orchestrate work by calling actors. - Expect
actionin actor APIs, timers, cleanup hooks, and callbacks. - Keep pure logic separated from actor-driven orchestration code.
- Read the effect marker together with the argument and return types.
A useful mental model is pure <= mut <= proc, with
action <= proc on a separate branch. That is why pure
code can be used where a mutating or procedural callable is accepted,
and why actions participate in the broader effect system without being
the same thing as ordinary sequential procedures. When you design higher
order APIs, the effect on the callback is as important as its argument
types.
In practice, this means you should choose the weakest effect that describes the callable accurately. That keeps more code reusable and leaves the effect system useful as the codebase grows.