Skip to content

Expressions

mashin has a built-in expression language used inside compute steps, decide conditions, and ${...} interpolation in task strings. It is JavaScript-inspired: if you know JS, you already know most of it. The key difference is that expressions are pure. There are no I/O primitives, no fetch(), no console.log(). Expressions compute; machines effect.

Operators

Arithmetic

+ - * / %

Comparison

== != > < >= <=

In decide steps, you can also use natural-language operators: is, is not, is greater than, is less than, is at least, is at most.

Logical

&& || !

Natural-language: and, or, not.

Ternary

condition ? valueIfTrue : valueIfFalse

Let bindings

Bind intermediate values with let. Bindings are scoped to the current step:

compute analyze
let count = input.items.length
let total = input.items.reduce((sum, x) => sum + x.amount, 0)
let average = total / count
{count: count, total: total, average: average}

The last expression in a step is its return value.

Data access

ReferenceWhat it accesses
input.<field>Machine input fields
steps.<step_name>.<field>Output from a previous step
context.<key>Execution context values
state.<field>Machine state (reactive machines)
event.<field>Incoming event data (in subscribes handlers)

Dot access and bracket access both work: input.name and input["name"] are equivalent.

String interpolation

Inside with task strings (and any string using ${}), expressions are evaluated and inserted:

ask classify, using: "anthropic:claude-sonnet-4-6"
with task "Analyze this order.\n\nCustomer: ${input.customer_name}\nTotal: $${input.amount}\nItems: ${input.items.length}"

Array methods

Arrays support the standard functional methods:

compute transform
let names = input.users.map(u => u.name)
let active = input.users.filter(u => u.active)
let total = input.scores.reduce((sum, s) => sum + s, 0)
let found = input.items.find(i => i.id == input.target_id)
let has_admin = input.roles.includes("admin")
{
names: names,
active_count: active.length,
total_score: total,
target: found,
has_admin: has_admin
}
MethodDescription
.map(fn)Transform each element
.filter(fn)Keep elements that match
.reduce(fn, init)Accumulate a value
.find(fn)First matching element
.includes(val)Check if value exists
.lengthNumber of elements
.join(sep)Join elements into a string

Objects

Build objects with {} syntax. Use spread to merge:

compute merge
let base = {status: "active", created: now()}
let details = {name: input.name, email: input.email}
{...base, ...details}

Access nested fields with dot notation: steps.analyze.results[0].name.

Practical examples

Conditional formatting

compute format_price
let amount = input.price
{
display: amount > 1000
? "$" + (amount / 1000).toFixed(1) + "k"
: "$" + amount.toString(),
tier: amount > 10000 ? "enterprise" : amount > 1000 ? "business" : "starter"
}

Data reshaping

compute reshape
let grouped = input.orders.reduce((acc, order) => {
let key = order.status
let existing = acc[key] || []
return {...acc, [key]: [...existing, order]}
}, {})
{by_status: grouped, total: input.orders.length}

Filtering and scoring

compute score_candidates
let scored = input.candidates.map(c => ({
...c,
score: c.experience * 0.4 + c.test_result * 0.6
}))
let qualified = scored.filter(c => c.score >= 70)
{
qualified: qualified,
qualified_count: qualified.length,
best: qualified.reduce((best, c) => c.score > best.score ? c : best, qualified[0])
}

Where expressions are used

  • compute steps: the entire body is an expression
  • decide conditions: when <expression>
  • with task strings: ${expression} interpolation
  • Step configuration: values in returns, assuming, field defaults

Try it

Write a machine that accepts a list of numbers and uses a single compute step to return the count, sum, average, minimum, and maximum. Use let bindings for clarity.

Next steps