Judge
Judge and judge_all are more than just pattern matching.
Overview
judge and judge_all express decision tables directly in code.
judge: exclusive — first matching arm wins.judge_all: inclusive — all matching arms that evaluate truthy run (in source order) and, in expr-form, their results are collected.
Core Syntax
Judge - Exclusive
score | 95 grade | judge score >= 90: "A" score >= 80: "B" score >= 70: "C" else: "F" xx
score | 95 grade | judge score >= 90: "A" score >= 80: "B" score >= 70: "C" else: "F" xx
Evaluates top-to-bottom, stops on first truthy condition.
Judge All - Inclusive
balance | 45 trial_days | 5 has_new_features | true maintenance_scheduled | false notifications | judge_all balance < 50: "Low balance warning" trial_days < 7: "Trial expiring soon" has_new_features: "Check out new tools" maintenance_scheduled: "Maintenance tonight" else: "All systems normal" xx :say(notifications)
balance | 45 trial_days | 5 has_new_features | true maintenance_scheduled | false notifications | judge_all balance < 50: "Low balance warning" trial_days < 7: "Trial expiring soon" has_new_features: "Check out new tools" maintenance_scheduled: "Maintenance tonight" else: "All systems normal" xx :say(notifications)
All truthy arms run; expression-form returns an array of results in arm order.
Subject Shorthand: Judge Using
Avoid repeating the same subject on every arm.
score | 95 judge using score >= 90: :say("A") >= 80: :say("B") >= 70: :say("C") else: :say("F") xx
score | 95 judge using score >= 90: :say("A") >= 80: :say("B") >= 70: :say("C") else: :say("F") xx
Also works with judge_all:
x | -3 judge_all using x > 0: :say("positive") < 0: :say("negative") else: :say("zero") xx
x | -3 judge_all using x > 0: :say("positive") < 0: :say("negative") else: :say("zero") xx
Enum Matching
Enums in Goblin can be matched by name using using <EnumName>.
enum Status idle loading ready error xx enum Priority low medium high critical xx status | Status::idle judge status using Status idle: :say("System is idle") loading: :say("Processing...") ready: :say("Ready to proceed") error: :say("Error occurred") xx priority | Priority::high judge priority using Priority low: :say("Low priority") medium: :say("Medium priority") high: :say("High priority alert") critical: :say("CRITICAL - Immediate action required") xx
enum Status idle loading ready error xx enum Priority low medium high critical xx status | Status::idle judge status using Status idle: :say("System is idle") loading: :say("Processing...") ready: :say("Ready to proceed") error: :say("Error occurred") xx priority | Priority::high judge priority using Priority low: :say("Low priority") medium: :say("Medium priority") high: :say("High priority alert") critical: :say("CRITICAL - Immediate action required") xx
(Future: tagged-union payload destructuring & guards — planned)
Arm Bodies: Expression, Statement, or Block
x | 42 hit | "none" judge x > 100: hit |= "gt100" x > 40: "expression-only" x > 0: :say("block ok") hit |= "gt0" else: hit |= "else" xx
x | 42 hit | "none" judge x > 100: hit |= "gt100" x > 40: "expression-only" x > 0: :say("block ok") hit |= "gt0" else: hit |= "else" xx
Inline expression result propagates as the arm's return value (see Dispatcher Pattern below). Inline statement executes immediately. Block runs all contained statements.
Control flow (return, break, continue) propagates as expected; in judge_all, a return short-circuits remaining arms.
Return Propagation
When a judge arm contains a bare expression — a function call, a value, or any expression that produces a result — that result automatically propagates out of the enclosing action. You do not need an explicit return.
act double(x) => x * 2 act apply(n) judge n > 0: double(n) else: 0 xx xx :say(apply(5)) /// 10 :say(apply(-1)) /// 0
act double(x) => x * 2 act apply(n) judge n > 0: double(n) else: 0 xx xx :say(apply(5)) /// 10 :say(apply(-1)) /// 0
This makes judge the natural tool for dispatching — the arm fires, the handler runs, and its return value becomes the action's return value without extra ceremony.
Dispatcher Pattern
Because arm expressions propagate their return values, judge is the cleanest way to dispatch to handlers based on a condition. The dispatcher itself stays readable while the complexity lives in isolated handler actions.
act resolve_link(inner, ctx, portal) judge :starts_with(inner, "image:"): resolve_image_link(inner, portal) :starts_with(inner, "video:"): resolve_video_link(inner, portal) :starts_with(inner, ["http://", "https://"]): resolve_external_link(inner) else: resolve_page_link(inner, ctx, portal) xx xx
act resolve_link(inner, ctx, portal) judge :starts_with(inner, "image:"): resolve_image_link(inner, portal) :starts_with(inner, "video:"): resolve_video_link(inner, portal) :starts_with(inner, ["http://", "https://"]): resolve_external_link(inner) else: resolve_page_link(inner, ctx, portal) xx xx
Each handler receives exactly what it needs and returns its result. The dispatcher is just a table — condition on the left, handler on the right.
Header Return
A shared return value for empty matching arms.
judge return "ERR" a < 0: b < 0: c < 0: xx
judge return "ERR" a < 0: b < 0: c < 0: xx
Equivalent to inlining return "ERR" in each empty arm. Works with both judge and judge_all.
Evaluation Semantics
| Construct | Matching Policy | Execution | Expression Result |
|---|---|---|---|
judge |
First truthy arm wins | Stops after first match | Value of arm (or nil) |
judge_all |
All truthy arms | Runs all in source order | Array of arm results |
Truthiness uses standard Goblin rules (false/nil falsey; everything else truthy). else matches when no prior arm matched.
Rich Condition Syntax
Logical ops and, or, not and the <> OR sugar all work inside arm conditions:
shipping_cost | judge weight >= 20: 25.00 weight >= 5: 12.00 weight >= 1: 8.00 else: 5.00 xx
shipping_cost | judge weight >= 20: 25.00 weight >= 5: 12.00 weight >= 1: 8.00 else: 5.00 xx
Field access with >>:
user_status | judge user >> "is_premium": "premium" user >> "trial_expired": "expired" user >> "score" < 50: "basic" else: "standard" xx
user_status | judge user >> "is_premium": "premium" user >> "trial_expired": "expired" user >> "score" < 50: "basic" else: "standard" xx
Method-Chaining with Judge
Judge meshes with fluent chains for business rules:
order | judge fraud_check(order): order.hold.send_alert order >> "priority" >= 9: order.expedite.notify_team payment_failed(order): order.retry_payment.log_attempt inventory_check(order): order.fulfill.notify_customer else: order.schedule_standard xx
order | judge fraud_check(order): order.hold.send_alert order >> "priority" >= 9: order.expedite.notify_team payment_failed(order): order.retry_payment.log_attempt inventory_check(order): order.fulfill.notify_customer else: order.schedule_standard xx
Nested decisions remain readable:
result | judge user >> "is_premium": data.enrich.prioritize.cache user >> "is_trial": data.basic_clean.rate_limit user >> "score" < 50: judge data.size > 1000: data.throttle else: data.process_normally xx else: data.standard_process xx
result | judge user >> "is_premium": data.enrich.prioritize.cache user >> "is_trial": data.basic_clean.rate_limit user >> "score" < 50: judge data.size > 1000: data.throttle else: data.process_normally xx else: data.standard_process xx
Wrap judge in an action to make a reusable rule set:
act pricing_rules(cart) judge_all cart >> "total" >= 200: cart.apply_discount(15) cart >> "vip": cart.apply_discount(10) cart >> "items" > 5: cart.free_shipping else: cart.add_shipping(5) xx xx act checkout(cart) pricing_rules(cart) cart.charge xx
act pricing_rules(cart) judge_all cart >> "total" >= 200: cart.apply_discount(15) cart >> "vip": cart.apply_discount(10) cart >> "items" > 5: cart.free_shipping else: cart.add_shipping(5) xx xx act checkout(cart) pricing_rules(cart) cart.charge xx
Future: Weighted Judge All
actions | judge_all [10] balance >> "amount" < 10: fraud_alert(transaction) [5] user >> "new_signup": welcome_email(user) [1] analytics >> "needed": log_event(transaction) else: standard_processing xx
actions | judge_all [10] balance >> "amount" < 10: fraud_alert(transaction) [5] user >> "new_signup": welcome_email(user) [1] analytics >> "needed": log_event(transaction) else: standard_processing xx
Sort by descending weight; equal weights keep source order.
Status: Planned for post–v1.0; syntax reserved.
Future: Header Do Stmt Default
err_count | 0 judge do err_count |= err_count + 1 bad_input(): timed_out(): else: :say("ok") xx
err_count | 0 judge do err_count |= err_count + 1 bad_input(): timed_out(): else: :say("ok") xx
Additional Roadmap (Post–v1.0)
Stateful vs Snapshot
mode | "cold" judge_all stateful mode == "cold": :say("warming") mode |= "warm" mode == "warm": :say("now warm") xx
mode | "cold" judge_all stateful mode == "cold": :say("warming") mode |= "warm" mode == "warm": :say("now warm") xx
Enum Payloads (Tagged Unions)
ev | Event::Click(10, 20) judge ev using Event Click(x, y) if x > 0 and y > 0: :say("clicked @ " + x + "," + y) Resize(w, h) if w*h > 10000: :say("big resize") Key(code) if code == "Enter": :say("enter pressed") Idle: :say("idle") xx
ev | Event::Click(10, 20) judge ev using Event Click(x, y) if x > 0 and y > 0: :say("clicked @ " + x + "," + y) Resize(w, h) if w*h > 10000: :say("big resize") Key(code) if code == "Enter": :say("enter pressed") Idle: :say("idle") xx
Implementation Status Summary
| Feature | Status |
|---|---|
judge / judge_all |
✅ |
judge using |
✅ |
| Enum name matching | ✅ |
| Mixed expr/stmt/block bodies | ✅ |
Header return <expr> |
✅ |
| Control-flow propagation | ✅ |
| Arm expression return propagation | ✅ |
| Dispatcher pattern | ✅ |
Weighted arms [n] |
🔜 planned (post–v1.0) |
Header do <stmt> default |
🔜 planned |
judge_all stateful mode |
🔜 planned |
| Structured results (expr-form) | 🔜 planned |
| Hot-reloadable arms | 🔜 planned |
| Enum payload destructuring | 🔜 planned |
Design principle: Other languages evaluate; Goblin judges.