ddonche/goblin-lang
0.46.24
1
0
docs keyword
[[judge]]

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.