The rule application layer applies simplification rules via MORK pattern/template matching.
Status: Design Documentation Last Updated: 2025-12-06
This layer uses MORK's transform_multi_multi_() function to apply pattern/template rewriting rules. Rules are organized by category and checked against analysis facts before application.
Input: Proc + AnalysisFacts
↓
┌──────────────────────────────────────┐
│ RULE APPLICATION LAYER │
│ │
│ ┌────────────────────────────────┐ │
│ │ Rule Registry │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │Algebraic │ │ Control │ │ │
│ │ │ Rules │ │ Flow │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │Type-Aware│ │ Rholang │ │ │
│ │ │ Rules │ │Congruence│ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────┐ │
│ │ MORK transform_multi_multi_ │ │
│ │ Pattern → Template rewrite │ │
│ └────────────────────────────────┘ │
│ │
└──────────────────────────────────────┘
↓
Output: Transformed Proc
simplification/rules/
├── mod.rs # Rule registry and dispatch
├── algebraic.rs # x+0→x, x*1→x, double-neg
├── control_flow.rs # if(true)→then, dead code
├── type_aware.rs # Redundant cast removal
├── beta_reduction.rs # Lambda application
├── inlining.rs # Let binding simplification
└── rholang/
├── mod.rs
├── nil_identity.rs # P|0 ≡ P
├── commutativity.rs # P|Q ≡ Q|P (canonical)
├── associativity.rs # (P|Q)|R ≡ P|(Q|R)
└── scope_extrusion.rs # new x in (P|Q) scope laws
/// Core trait for all simplification rules
pub trait SimplificationRule: Send + Sync {
/// Unique identifier for this rule
fn name(&self) -> &str;
/// Category for grouping and phase selection
fn category(&self) -> RuleCategory;
/// Which phase(s) this rule applies in
fn phase(&self) -> SimplificationPhase;
/// MORK pattern expression (what to match)
fn pattern(&self) -> Expr;
/// MORK template expression (what to produce)
fn template(&self) -> Expr;
/// Guard predicate - returns true if rule can apply
/// May use analysis facts for context-dependent rules
fn guard(&self, term: &Proc, facts: &AnalysisFacts) -> bool {
true // Default: always applicable if pattern matches
}
/// Check if transformation is beneficial
fn is_beneficial(&self, old: &Proc, new: &Proc, facts: &AnalysisFacts) -> bool {
// Default: beneficial if new is smaller
new.size() <= old.size()
}
/// Termination weight - must be negative for size-decreasing rules
fn termination_weight(&self) -> i32;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RuleCategory {
Algebraic,
ControlFlow,
TypeAware,
BetaReduction,
Inlining,
RholangCongruence,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SimplificationPhase {
LocalSimplification,
AnalysisDriven,
StructuralNormalization,
TypeDirected,
}
The core rewriting uses MORK's transformation function from MORK/kernel/src/space.rs:1221:
pub struct SimplificationEngine {
space: Space,
rules: RuleRegistry,
analysis_facts: AnalysisFacts,
}
impl SimplificationEngine {
/// Apply a single rule to a term
pub fn apply_rule(
&mut self,
rule: &dyn SimplificationRule,
term: &Proc,
) -> Option<Proc> {
// 1. Check guard predicate
if !rule.guard(term, &self.analysis_facts) {
return None;
}
// 2. Convert term to MORK expression
let term_expr = term.to_mork_expr();
// 3. Use MORK's transform_multi_multi_ for pattern/template rewrite
let (touched, changed) = self.space.transform_multi_multi_(
rule.pattern(),
rule.template(),
term_expr,
);
if !changed {
return None;
}
// 4. Extract result from space
let new_term = self.extract_result();
// 5. Check if transformation is beneficial
if rule.is_beneficial(term, &new_term, &self.analysis_facts) {
Some(new_term)
} else {
None
}
}
/// Apply all applicable rules until fixpoint
pub fn simplify(&mut self, term: Proc) -> Proc {
let mut current = term;
let mut visited = HashSet::new();
loop {
let hash = current.content_hash();
if visited.contains(&hash) {
break; // Cycle detected - stop
}
visited.insert(hash);
let mut changed = false;
for rule in self.rules.all_rules() {
if let Some(simplified) = self.apply_rule(rule, ¤t) {
current = simplified;
changed = true;
break; // Restart from highest priority
}
}
if !changed {
break; // Fixpoint reached
}
}
current
}
}
Rules are defined using MORK S-expression patterns:
impl SimplificationRule for AddZeroRight {
fn name(&self) -> &str { "add-zero-right" }
fn category(&self) -> RuleCategory { RuleCategory::Algebraic }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::LocalSimplification }
fn pattern(&self) -> Expr {
// (Plus ?x (Num 0))
parse_expr("(Plus $x (Num 0))")
}
fn template(&self) -> Expr {
// ?x
parse_expr("$x")
}
fn termination_weight(&self) -> i32 { -1 }
}
// algebraic.rs
pub struct AddZeroRight; // x + 0 → x
pub struct AddZeroLeft; // 0 + x → x
pub struct MulOneRight; // x * 1 → x
pub struct MulOneLeft; // 1 * x → x
pub struct MulZeroRight; // x * 0 → 0
pub struct MulZeroLeft; // 0 * x → 0
pub struct DoubleNeg; // --x → x
pub struct IdempotentAnd; // x && x → x
pub struct IdempotentOr; // x || x → x
impl SimplificationRule for MulZeroRight {
fn name(&self) -> &str { "mul-zero-right" }
fn category(&self) -> RuleCategory { RuleCategory::Algebraic }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::LocalSimplification }
fn pattern(&self) -> Expr {
parse_expr("(Mult $x (Num 0))")
}
fn template(&self) -> Expr {
parse_expr("(Num 0)")
}
fn termination_weight(&self) -> i32 { -2 } // Eliminates entire subterm
}
// control_flow.rs
pub struct IfTrue; // if(true) P else Q → P
pub struct IfFalse; // if(false) P else Q → Q
pub struct IfSameBranch; // if(c) P else P → P
pub struct DeadCodeAfterReturn; // return e; rest → return e
impl SimplificationRule for IfTrue {
fn name(&self) -> &str { "if-true" }
fn category(&self) -> RuleCategory { RuleCategory::ControlFlow }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::LocalSimplification }
fn pattern(&self) -> Expr {
parse_expr("(If (Bool true) $then $else)")
}
fn template(&self) -> Expr {
parse_expr("$then")
}
fn termination_weight(&self) -> i32 { -3 }
}
impl SimplificationRule for DeadCodeAfterReturn {
fn name(&self) -> &str { "dead-code-after-return" }
fn category(&self) -> RuleCategory { RuleCategory::ControlFlow }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::AnalysisDriven }
fn pattern(&self) -> Expr {
parse_expr("(Seq (Return $e) $rest)")
}
fn template(&self) -> Expr {
parse_expr("(Return $e)")
}
fn guard(&self, term: &Proc, facts: &AnalysisFacts) -> bool {
// Only apply if rest is actually unreachable
if let Proc::Seq(_, rest) = term {
facts.is_dead(rest.id())
} else {
false
}
}
fn termination_weight(&self) -> i32 { -1 }
}
// type_aware.rs
pub struct RedundantCast; // (T)x where x : T → x
pub struct IdentityInto; // x.into::<T>() where x : T → x
impl SimplificationRule for RedundantCast {
fn name(&self) -> &str { "redundant-cast" }
fn category(&self) -> RuleCategory { RuleCategory::TypeAware }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::TypeDirected }
fn pattern(&self) -> Expr {
parse_expr("(Cast $T $x)")
}
fn template(&self) -> Expr {
parse_expr("$x")
}
fn guard(&self, term: &Proc, facts: &AnalysisFacts) -> bool {
// Check if x already has type T
if let Proc::Cast(target_type, inner) = term {
facts.type_of(inner).map(|t| t == *target_type).unwrap_or(false)
} else {
false
}
}
fn termination_weight(&self) -> i32 { -1 }
}
// rholang/nil_identity.rs
pub struct NilIdentityRight; // P | 0 → P
pub struct NilIdentityLeft; // 0 | P → P
impl SimplificationRule for NilIdentityRight {
fn name(&self) -> &str { "nil-identity-right" }
fn category(&self) -> RuleCategory { RuleCategory::RholangCongruence }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::StructuralNormalization }
fn pattern(&self) -> Expr {
parse_expr("(Par $P (Nil))")
}
fn template(&self) -> Expr {
parse_expr("$P")
}
fn termination_weight(&self) -> i32 { -1 }
}
// rholang/commutativity.rs
pub struct ParCommute; // P | Q → Q | P (when order(P) > order(Q))
impl SimplificationRule for ParCommute {
fn name(&self) -> &str { "par-commute" }
fn category(&self) -> RuleCategory { RuleCategory::RholangCongruence }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::StructuralNormalization }
fn pattern(&self) -> Expr {
parse_expr("(Par $P $Q)")
}
fn template(&self) -> Expr {
parse_expr("(Par $Q $P)")
}
fn guard(&self, term: &Proc, _facts: &AnalysisFacts) -> bool {
// Only apply to reach canonical form
if let Proc::Par(p, q) = term {
term_order(p) > term_order(q)
} else {
false
}
}
fn termination_weight(&self) -> i32 { 0 } // Size-preserving
}
/// Canonical term ordering for commutativity
fn term_order(proc: &Proc) -> u64 {
proc.content_hash() // Use hash as stable ordering
}
// rholang/scope_extrusion.rs
pub struct ScopeExtrusion; // new x in (P | Q) → (new x in P) | Q when x ∉ FV(Q)
impl SimplificationRule for ScopeExtrusion {
fn name(&self) -> &str { "scope-extrusion" }
fn category(&self) -> RuleCategory { RuleCategory::RholangCongruence }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::StructuralNormalization }
fn pattern(&self) -> Expr {
parse_expr("(New $x (Par $P $Q))")
}
fn template(&self) -> Expr {
parse_expr("(Par (New $x $P) $Q)")
}
fn guard(&self, term: &Proc, _facts: &AnalysisFacts) -> bool {
if let Proc::New(x, body) = term {
if let Proc::Par(_, q) = body.as_ref() {
// x must not be free in Q
!q.free_vars().contains(x)
} else {
false
}
} else {
false
}
}
fn termination_weight(&self) -> i32 { 0 } // Size-preserving
}
// beta_reduction.rs
pub struct BetaReduction; // (λx.P)(Q) → P{Q/x}
impl SimplificationRule for BetaReduction {
fn name(&self) -> &str { "beta-reduction" }
fn category(&self) -> RuleCategory { RuleCategory::BetaReduction }
fn phase(&self) -> SimplificationPhase { SimplificationPhase::LocalSimplification }
fn pattern(&self) -> Expr {
parse_expr("(App (Lambda $x $body) $arg)")
}
fn template(&self) -> Expr {
// Template uses substitution
parse_expr("(Subst $body $x $arg)")
}
fn guard(&self, term: &Proc, facts: &AnalysisFacts) -> bool {
// Only inline if argument is small or used once
if let Proc::App(Proc::Lambda(x, body), arg) = term {
arg.size() <= 3 || body.use_count(x) <= 1
} else {
false
}
}
fn termination_weight(&self) -> i32 { -1 }
}
pub struct RuleRegistry {
rules: Vec<Box<dyn SimplificationRule>>,
}
impl RuleRegistry {
pub fn new() -> Self {
let mut registry = Self { rules: Vec::new() };
// Register all rules
registry.register(Box::new(AddZeroRight));
registry.register(Box::new(AddZeroLeft));
registry.register(Box::new(MulOneRight));
registry.register(Box::new(MulZeroRight));
registry.register(Box::new(DoubleNeg));
registry.register(Box::new(IfTrue));
registry.register(Box::new(IfFalse));
registry.register(Box::new(DeadCodeAfterReturn));
registry.register(Box::new(NilIdentityRight));
registry.register(Box::new(NilIdentityLeft));
registry.register(Box::new(ParCommute));
registry.register(Box::new(ScopeExtrusion));
registry.register(Box::new(BetaReduction));
// ... more rules
registry
}
pub fn register(&mut self, rule: Box<dyn SimplificationRule>) {
self.rules.push(rule);
}
pub fn rules_for_phase(&self, phase: SimplificationPhase) -> Vec<&dyn SimplificationRule> {
self.rules.iter()
.filter(|r| r.phase() == phase)
.map(|r| r.as_ref())
.collect()
}
pub fn all_rules(&self) -> impl Iterator<Item = &dyn SimplificationRule> {
self.rules.iter().map(|r| r.as_ref())
}
}
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |