aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortslil <tslil@posteo.de>2026-03-21 15:10:05 +0000
committertslil <tslil@posteo.de>2026-03-21 17:28:51 +0000
commitfb0d0b91bfd45d1911e993ca753d81da5e60f60c (patch)
tree258da432a1b00a399d8f3b9afd1f4504f4d20eee
parentc22cc3b348bdcb446fb0e44070da36fdf6b05800 (diff)
fixing evaluation semantics
-rw-r--r--rprt-engine/src/evaluate.rs33
-rw-r--r--rprt.md30
2 files changed, 45 insertions, 18 deletions
diff --git a/rprt-engine/src/evaluate.rs b/rprt-engine/src/evaluate.rs
index f6fee8a..b23c1be 100644
--- a/rprt-engine/src/evaluate.rs
+++ b/rprt-engine/src/evaluate.rs
@@ -11,6 +11,8 @@ use thiserror::Error;
pub enum EvaluationError {
#[error("{0}")]
SelectionFunctionError(VectoriseError<SFError>),
+ #[error("Invalid right-only application")]
+ IROApplication,
#[error("Not yet implemented {0}")]
UnimplementedError(&'static str),
}
@@ -21,6 +23,10 @@ pub fn evaluate(
left: Option<Selection>,
right: Option<Selection>,
) -> Result<StateResult, EvaluationError> {
+ if left.is_none() && right.is_some() {
+ return Err(EvaluationError::IROApplication);
+ };
+
match comp {
Composite::SelectionFunction {
func,
@@ -36,8 +42,29 @@ pub fn evaluate(
Err(EvaluationError::UnimplementedError("text function"))
}
Composite::Hook { kind, left, right } => Err(EvaluationError::UnimplementedError("hook")),
- Composite::Train2 { f, g } => Err(EvaluationError::UnimplementedError("train2")),
- Composite::Train3 { f, g, h } => Err(EvaluationError::UnimplementedError("train3")),
- Composite::Group { operations } => Err(EvaluationError::UnimplementedError("group")),
+ Composite::Train2 { f, g } => {
+ let (left, mut state) = evaluate(es, *f, left, right)?;
+ let (sel, more_state) = evaluate(es, *g, Some(left), None)?;
+ state.extend(more_state);
+ Ok((sel, state))
+ }
+ Composite::Train3 { f, g, h } => {
+ let (f_left, mut f_state) = evaluate(es, *f, left.clone(), right.clone())?;
+ let (g_right, h_state) = evaluate(es, *h, left, right)?;
+ let (sel, g_state) = evaluate(es, *g, Some(f_left), Some(g_right))?;
+ f_state.extend(h_state);
+ f_state.extend(g_state);
+ Ok((sel, f_state))
+ }
+ Composite::Group { operations } => {
+ // TODO: does rust have some monadic failure map thing on first failure?
+ let mut selections = Vec::new();
+ let mut states = Vec::new();
+ for f in operations {
+ let (sel, state) = evaluate(es, f, left.clone(), right.clone())?;
+ selections.extend(sel);
+ states.extend(state);
+ }
+ }
}
}
diff --git a/rprt.md b/rprt.md
index 12449ca..31349dd 100644
--- a/rprt.md
+++ b/rprt.md
@@ -75,32 +75,32 @@ The buffer axis (rank 3) is the **leading axis**: operations on rank 3 selection
A **train** is a sequence of functions that compose according to specific rules, following [APL train syntax](https://aplwiki.com/wiki/Train).
**2-train (Atop)** `F G`:
-- Niladic: `(F G) = F (G)`
-- Monadic: `(F G) ω = F (G ω)`
-- Dyadic: `α (F G) ω = F (α G ω)`
+- Niladic: `F G` = (F) G`; `F` is evaluated niladically and `G` is evaluated monadically on the result
+- Monadic: `ω (F G) = (ω F) G`
+- Dyadic: `ω (F G) α = (ω F α) G`
**3-train (Fork)** `F G H`:
-- Niladic: `(F G H) = (F) G (H)`
-- Monadic: `(F G H) ω = (F ω) G (H ω)`
-- Dyadic: `α (F G H) ω = (α F ω) G (α H ω)`
+- Niladic: `F G H` = `(F) G (H)`; `F`, `H` are evaluated niladically and `G` is evaluated dyadically on the result
+- Monadic: `ω (F G H) = (ω F) G (ω H)`
+- Dyadic: `ω (F G H) α = (ω F α) G (ω H α)`
-**Longer trains**: Parsed right-associatively. A train of n functions is parsed by taking the rightmost 3 functions as a fork (if n is odd and ≥3) or the rightmost 2 functions as atop (if n is even), then recursively parsing the remaining functions as the left part. For example:
-- 4 functions `F G H I` → `F (G H I)` (atop of F with a fork)
-- 5 functions `F G H I J` → `(F G) (H I J)` (atop of an atop with a fork)
+**Longer trains**: Parsed left-associatively. A train of n functions is parsed by taking the leftmost 3 functions as a fork (if n is odd and ≥3) or the leftmost 2 functions as atop (if n is even), then recursively parsing the remaining functions as the right part. For example:
+- 4 functions `F G H I` → `(F G H) I` (I atop a fork)
+- 5 functions `F G H I J` → `(F G H) (I J)` (atop, atop a fork)
### Hooks
RPRT provides two explicit hook combinators from BQN for flexible function composition:
**Before (Left Hook)** `F>G`:
-- Niladic: `(F>G) = (F) G`
-- Monadic: `(F>G) ω = (F ω) G ω`
-- Dyadic: `α (F>G) ω = (F α) G ω`
+- Niladic: `F>G = (F) G`; `F` is evaluated niladically and `G` is evaluated monadically on the result
+- Monadic: `ω (F>G) = (ω F) G ω`
+- Dyadic: `ω (F>G) α = (ω F) G α`
**After (Right Hook)** `F<G`:
-- Niladic: `(F<G) = F (G)`
-- Monadic: `(F<G) ω = ω F (G ω)`
-- Dyadic: `α (F<G) ω = α F (G ω)`
+- Niladic: `F<G = ???` TODO: undefined?
+- Monadic: `ω (F<G) = ω F (G ω)`
+- Dyadic: `ω (F<G) α = ω F (G α)`
These combinators bind more tightly than trains and enable partial application patterns. Hooks are **right-associative**, the _opposite_ of BQN's modifier associativity: `F<G<H` parses as `F<(G<H)`.