From 5d6916b20c3097558f21693c05a1d0293bea17a3 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 13 Jun 2026 18:09:24 +0200 Subject: [PATCH] fix(iter): map-left/map-right iterate the named operand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit map-left and map-right were defined by which argument is held fixed, the inverse of the conventional model and the source of the long-standing confusion (a scalar in the 'wrong' slot, or a lambda that does not broadcast, gave surprising results). Redefine them by which operand is ITERATED: map-left — iterate the left operand, hold the right whole → fn(left_i, right) map-right — iterate the right operand, hold the left whole → fn(left, right_i) The iterated side is fixed by the operator (no auto-detect heuristic). When that side is an atom there is nothing to iterate, so fn is applied once to (left, right); for an atomic builtin that single call is exactly its broadcast, so map-left/map-right agree with plain application there. Every call routes through call_fn2, so atomic builtins broadcast a held vector element-wise and lambdas receive their arguments whole. This swaps the prior meaning of the two ops. The window-join interval idiom built its N per-row [lo hi] intervals with the old map-left; that role is now map-right, so the join tests/docs are migrated map-left -> map-right. Updates the map-left/map-right reference docs and adds non-broadcasting lambda regression tests (a single concat / list-building fn exposes the iteration structure that arithmetic builtins' broadcast otherwise hides). --- docs/docs/language/functions.md | 4 +-- docs/docs/queries/joins.md | 4 +-- docs/docs/reference/all-functions.md | 4 +-- src/lang/eval.c | 4 +-- src/ops/collection.c | 30 ++++++++++--------- src/ops/query.c | 2 +- test/rfl/collection/cov2.rfl | 25 ++++++++++++---- test/rfl/integration/joins.rfl | 38 ++++++++++++------------ test/rfl/io/dump.rfl | 2 +- test/rfl/ops/exec_coverage.rfl | 2 +- test/rfl/ops/opt_advanced.rfl | 2 +- test/rfl/ops/opt_branch_cov.rfl | 2 +- test/rfl/ops/query_coverage.rfl | 4 +-- test/rfl/query/query_branch_cov.rfl | 2 +- test/rfl/query/query_update_coverage.rfl | 6 ++-- test/rfl/table/update.rfl | 2 +- 16 files changed, 74 insertions(+), 59 deletions(-) diff --git a/docs/docs/language/functions.md b/docs/docs/language/functions.md index 546b0ba6..2b87d46b 100644 --- a/docs/docs/language/functions.md +++ b/docs/docs/language/functions.md @@ -93,8 +93,8 @@ Functions that take other functions as arguments. | `scan-left` | variadic | Left-to-right running fold | `(scan-left + (enlist 1 2 3))` → `[1 3 6]` | | `scan-right` | variadic | Right-to-left running fold | `(scan-right + (enlist 1 2 3))` → `[6 5 3]` | | `apply` | variadic | Zip-apply function pairwise over two lists | `(apply + (enlist 1 2) (enlist 3 4))` → `[4 6]` | -| `map-left` | variadic | Map with left argument fixed | `(map-left + 10 [1 2 3])` → `[11 12 13]` | -| `map-right` | variadic | Map with right argument fixed | `(map-right - [10 20 30] 5)` → `[5 15 25]` | +| `map-left` | variadic | Map each element of the left over the whole right | `(map-left + 10 [1 2 3])` → `[11 12 13]` | +| `map-right` | variadic | Map the whole left over each element of the right | `(map-right - [10 20 30] 5)` → `[5 15 25]` | ## Collection Operations diff --git a/docs/docs/queries/joins.md b/docs/docs/queries/joins.md index 3b674c55..0ded9636 100644 --- a/docs/docs/queries/joins.md +++ b/docs/docs/queries/joins.md @@ -185,7 +185,7 @@ Window join with per-row time intervals. `intervals` is a list of two vectors `[ [928 528 648 914 918 626 577 817 620 698]))) ; Build per-row intervals: [lo, hi] for each trade -(set intervals (map-left + [-1000 1000] (at trades 'Time))) +(set intervals (map-right + [-1000 1000] (at trades 'Time))) ; Window join: min bid and max ask within each window (window-join [Sym Time] intervals trades quotes @@ -202,7 +202,7 @@ Window join with per-row time intervals. `intervals` is a list of two vectors `[ (list bsym btime bid ask))) ; Build intervals from the trades timestamp -(set intervals (map-left + [-1000 1000] (at trades 'Ts))) +(set intervals (map-right + [-1000 1000] (at trades 'Ts))) (window-join [Sym Ts] intervals trades quotes {bid: (min Bid) ask: (max Ask)}) diff --git a/docs/docs/reference/all-functions.md b/docs/docs/reference/all-functions.md index 6972523a..649b37da 100644 --- a/docs/docs/reference/all-functions.md +++ b/docs/docs/reference/all-functions.md @@ -140,8 +140,8 @@ Functions that take other functions as arguments for mapping, folding, and filte | `scan-right` | variadic | — | Right-to-left running fold | `(scan-right + (enlist 1 2 3))` → `[6 5 3]` | | `filter` | binary | — | Keep elements where boolean mask is true | `(filter [1 2 3 4] (> [1 2 3 4] 2))` → `[3 4]` | | `apply` | variadic | — | Zip-apply function pairwise over lists | `(apply + (enlist 1 2) (enlist 3 4))` → `[4 6]` | -| `map-left` | variadic | — | Map with left argument fixed | `(map-left + 10 [1 2 3])` → `[11 12 13]` | -| `map-right` | variadic | — | Map with right argument fixed | `(map-right - [10 20 30] 5)` → `[5 15 25]` | +| `map-left` | variadic | — | Map each element of the left over the whole right | `(map-left + 10 [1 2 3])` → `[11 12 13]` | +| `map-right` | variadic | — | Map the whole left over each element of the right | `(map-right - [10 20 30] 5)` → `[5 15 25]` | ```lisp ; Transform each row with map diff --git a/src/lang/eval.c b/src/lang/eval.c index a0ec8d0c..496143c9 100644 --- a/src/lang/eval.c +++ b/src/lang/eval.c @@ -1331,8 +1331,8 @@ ray_t* ray_table_fn(ray_t* names, ray_t* cols) { { ray_release(tbl); if (_bxn) ray_release(_bxn); if (_bxc) ray_release(_bxc); return ray_error("domain", NULL); } /* Empty generic list → typeless empty column: keep it as a RAY_LIST - * so its storage type is adopted from the first inserted value - * (q-style () column), rather than defaulting to I64. */ + * so its storage type is adopted from the first inserted value, + * rather than defaulting to I64. */ if (nrows == 0) { ray_retain(col_src); tbl = ray_table_add_col(tbl, name_id, col_src); diff --git a/src/ops/collection.c b/src/ops/collection.c index f9f5410a..381d6fbc 100644 --- a/src/ops/collection.c +++ b/src/ops/collection.c @@ -2200,26 +2200,28 @@ static ray_t* map_iterate(ray_t* fn, ray_t* fixed, ray_t* vec, int fixed_is_left return out; } -/* (map-left fn left right) → fix the LEFT arg, iterate over the right: - * apply fn(left, right_i) for each element of right. If right is scalar this - * collapses to a single fn(left, right) (handled by map_iterate). */ +/* (map-left fn left right): iterate the LEFT operand, pairing each element with + * the whole RIGHT → fn(left_i, right). + * (map-right fn left right): iterate the RIGHT operand, pairing the whole LEFT + * with each element → fn(left, right_i). + * + * The iterated side is fixed by the operator, not auto-detected. When that side + * is an atom there is nothing to iterate, so fn is applied once to (left,right); + * for an atomic fn that single call is exactly its broadcast over the other + * operand, so map-left/map-right agree with plain atomic application there. + * Every call goes through call_fn2, which routes atomic builtins through the + * broadcasting engine (so a held vector conforms element-wise) and hands + * lambdas their arguments whole. */ ray_t* ray_map_left_fn(ray_t** args, int64_t n) { if (n != 3) return ray_error("domain", NULL); - ray_t* fn = args[0]; - ray_t* left = args[1]; - ray_t* right = args[2]; - return map_iterate(fn, left, right, 1); /* fn(left, right_i) */ + /* iterate left (vec), hold right (fixed) → fn(left_i, right) */ + return map_iterate(args[0], args[2], args[1], 0); } -/* (map-right fn left right) → fix the RIGHT arg, iterate over the left: - * apply fn(left_i, right) for each element of left. If left is scalar this - * collapses to a single fn(left, right) (handled by map_iterate). */ ray_t* ray_map_right_fn(ray_t** args, int64_t n) { if (n != 3) return ray_error("domain", NULL); - ray_t* fn = args[0]; - ray_t* left = args[1]; - ray_t* right = args[2]; - return map_iterate(fn, right, left, 0); /* fn(left_i, right) */ + /* iterate right (vec), hold left (fixed) → fn(left, right_i) */ + return map_iterate(args[0], args[1], args[2], 1); } /* ══════════════════════════════════════════ diff --git a/src/ops/query.c b/src/ops/query.c index b5d67bd2..34525440 100644 --- a/src/ops/query.c +++ b/src/ops/query.c @@ -9198,7 +9198,7 @@ ray_t* ray_xbar_fn(ray_t* col, ray_t* bucket) { * ══════════════════════════════════════════ */ /* Derive the storage type for a typeless (empty RAY_LIST) column from the - * first value inserted into it — q-style () columns adopt their type on the + * first value inserted into it — an empty () column adopts its type on the * first insert. Returns the RAY_* column type, or RAY_LIST when the payload * is itself nested (non-atom elements → a genuine list column). */ static int8_t typeless_col_type(ray_t* payload) { diff --git a/test/rfl/collection/cov2.rfl b/test/rfl/collection/cov2.rfl index 9ad6813d..ad5a5f86 100644 --- a/test/rfl/collection/cov2.rfl +++ b/test/rfl/collection/cov2.rfl @@ -201,30 +201,43 @@ (count (binr [1 3 5] [0 2 4])) -- 3 ;; ════════════════════════════════════════════════════════════════ -;; 13. map-left (lines 2108-2121) +;; 13. map-left: iterate the LEFT, hold the right whole, calling fn(left_i, +;; right). An atom on the left iterates once, which for an atomic fn is +;; identical to plain broadcast application. ;; ════════════════════════════════════════════════════════════════ -;; fn fixed vec: fn(fixed, elem) for each elem (map-left + 10 (list 1 2 3)) -- (list 11 12 13) (map-left * 3 (list 2 4 6)) -- (list 6 12 18) ;; map-left arity error (try (map-left + 1) (fn [e] "err")) -- "err" -;; map-left auto-detect: vec is scalar, fixed is vector → swap roles +;; Iterate the left vector, holding the right scalar. (map-left + (list 1 2 3) 5) -- (list 6 7 8) (map-left - (list 10 20 30) 5) -- (list 5 15 25) +;; Non-broadcasting lambda exposes the structure builtins hide. Iterating the +;; left vector yields one cell per element; an atom left applies fn once. +(map-left (fn [x y] (list x y)) [1 2 3] 10) -- (list [1 10] [2 10] [3 10]) +(map-left (fn [x y] (list x y)) 10 [1 2 3]) -- (list 10 [1 2 3]) ;; ════════════════════════════════════════════════════════════════ -;; 14. map-right (lines 2125-2138) +;; 14. map-right: iterate the RIGHT, hold the left whole, calling fn(left, +;; right_i). An atom on the right iterates once, which for an atomic fn is +;; identical to plain broadcast application. ;; ════════════════════════════════════════════════════════════════ -;; fn vec fixed: fn(elem, fixed) for each elem (map-right - (list 10 20 30) 3) -- (list 7 17 27) (map-right + (list 1 2 3) 100) -- (list 101 102 103) ;; map-right arity error (try (map-right + 1) (fn [e] "err")) -- "err" -;; map-right auto-detect: vec is scalar, fixed is vector → iterate fixed +;; Iterate the right vector, holding the left scalar. (map-right + 5 (list 1 2 3)) -- (list 6 7 8) (map-right - 100 (list 1 2 3)) -- (list 99 98 97) +;; Non-broadcasting lambda: iterating the right vector pairs the whole left with +;; each element (fn order is source order); an atom right applies fn once. +(map-right (fn [x y] (list x y)) 10 [1 2 3]) -- (list [10 1] [10 2] [10 3]) +(map-right (fn [x y] (list x y)) [1 2 3] 10) -- (list [1 2 3] 10) +;; concat is non-broadcasting: iterating the right yields one concat(10,e) per +;; element ([10 e]); a single concat(10,[1 2 3]) would instead flatten. +(map-right concat 10 [1 2 3]) -- (list [10 1] [10 2] [10 3]) ;; ════════════════════════════════════════════════════════════════ ;; 15. map-iterate scalar path (line 2070-2075): diff --git a/test/rfl/integration/joins.rfl b/test/rfl/integration/joins.rfl index 7f03ac91..fbafe89a 100644 --- a/test/rfl/integration/joins.rfl +++ b/test/rfl/integration/joins.rfl @@ -74,45 +74,45 @@ (sum (at ijmk 'val1)) -- 400 (sum (at ijmk 'val2)) -- 4000 ;; window-join -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 101] +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 101] ;; window-join1 -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(at (window-join1 [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 101] +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(at (window-join1 [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 101] ;; window-join with multiple aggregations — both columns must be present -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'lo) -- [99 101] -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'hi) -- [111 112] +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'lo) -- [99 101] +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'hi) -- [111 112] ;; window-join rayforce1 canonical example (docs/queries-joins.html) ;; trades at 12:00:01, 12:00:04, 12:00:06 ± 1s windows ;; quotes at 12:00:00..12:00:09 sizes [928 528 648 914 918 626 577 817 620 698] ;; trade @ 01 window [00,02] -> sizes [928 528 648], min=528, max=928 ;; trade @ 04 window [03,05] -> sizes [914 918 626], min=626, max=918 ;; trade @ 06 window [05,07] -> sizes [626 577 817], min=577, max=817 -(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_min) -- [528 626 577] -(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_max) -- [928 918 817] +(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_min) -- [528 626 577] +(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_max) -- [928 918 817] ;; window-join sum/count/avg over the same canonical example ;; trade @ 01 window [00,02]: sum=2104 count=3 avg≈701.3333 ;; trade @ 04 window [03,05]: sum=2458 count=3 avg≈819.3333 ;; trade @ 06 window [05,07]: sum=2020 count=3 avg≈673.3333 -(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {s: (sum Size)}) 's) -- [2104 2458 2020] -(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Size)}) 'c) -- [3 3 3] +(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {s: (sum Size)}) 's) -- [2104 2458 2020] +(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Size)}) 'c) -- [3 3 3] ;; window-join first/last over the canonical example ;; trade @ 01 window [00,02]: first=928 last=648 ;; trade @ 04 window [03,05]: first=914 last=626 ;; trade @ 06 window [05,07]: first=626 last=817 -(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {f: (first Size)}) 'f) -- [928 914 626] -(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {l: (last Size)}) 'l) -- [648 626 817] +(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {f: (first Size)}) 'f) -- [928 914 626] +(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {l: (last Size)}) 'l) -- [648 626 817] ;; window-join raw bare-column form must accept non-numeric columns (regression) -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {tags: Tag}) 'tags)) -- 2 +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {tags: Tag}) 'tags)) -- 2 ;; window-join (count Col) must accept non-numeric source columns. ;; trades at 10:00:01 and 10:00:05 with ±1s windows; Tag rows at 10:00:00/02/04: ;; trade @ 01 window [00, 02] -> matches at 00 and 02 (2) ;; trade @ 05 window [04, 06] -> matches at 04 (1) -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {n: (count Tag)}) 'n) -- [2 1] +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {n: (count Tag)}) 'n) -- [2 1] ;; window-join COUNT must include window matches whose source value is null ;; ((count Col) is COUNT(*) semantics, not COUNT(non-null Col)). ;; trade @ 01 window [00, 02]: Bid rows at 00(99) and 02(NULL) -> count=2, min=99 ;; trade @ 05 window [04, 06]: Bid row at 04(101) -> count=1, min=101 -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Bid)}) 'c) -- [2 1] -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {m: (min Bid)}) 'm) -- [99 101] +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Bid)}) 'c) -- [2 1] +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {m: (min Bid)}) 'm) -- [99 101] ;; stddev/stddev_pop/var/var_pop must work as standalone aggregation ;; builtins so resolve_agg_opcode support in window-join is fully backed ;; by an eval-level implementation. @@ -136,14 +136,14 @@ ;; if the compare assumed 8-byte stride). ;; AAPL trade @ 01 window [00, 02]: AAPL quotes at 00, 02 -> min=100 max=201 ;; MSFT trade @ 01 window [00, 02]: MSFT quotes at 00, 02 -> min=300 max=401 -(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {lo: (min Bid)}) 'lo) -- [100 300] -(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {hi: (max Ask)}) 'hi) -- [201 401] +(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {lo: (min Bid)}) 'lo) -- [100 300] +(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {hi: (max Ask)}) 'hi) -- [201 401] ;; window-join with raw column (TYPE_MAPGROUP) -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2 +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2 ;; window-join1 with raw column (TYPE_MAPGROUP) -(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(count (at (window-join1 [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2 +(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(count (at (window-join1 [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2 ;; window-join with Enum columns (xasc converts Enum to Symbol) -(set trades (table [s time price] (list ['a 'a 'b] [10:00:01.000 10:00:05.000 10:00:03.000] [100 200 150])))(set quotes (table [s time bid] (list ['a 'a 'a 'b 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:01.000 10:00:04.000] [99 100 101 149 151])))(set intervals (map-left + [-2000 2000] (at trades 'time)))(at (window-join [s time] intervals trades quotes {minBid: (min bid)}) 'minBid) -- [99 101 149] +(set trades (table [s time price] (list ['a 'a 'b] [10:00:01.000 10:00:05.000 10:00:03.000] [100 200 150])))(set quotes (table [s time bid] (list ['a 'a 'a 'b 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:01.000 10:00:04.000] [99 100 101 149 151])))(set intervals (map-right + [-2000 2000] (at trades 'time)))(at (window-join [s time] intervals trades quotes {minBid: (min bid)}) 'minBid) -- [99 101 149] ;; empty left table (set t1 (table [id val1] (list (take [1] 0) (take [1] 0))))(set t2 (table [id val2] (list [1 2 3] [100 200 300])))(count (left-join [id] t1 t2)) -- 0 ;; empty right table — left-join keeps all 3 left rows with val2 = Null. diff --git a/test/rfl/io/dump.rfl b/test/rfl/io/dump.rfl index 8324d8d9..714df328 100644 --- a/test/rfl/io/dump.rfl +++ b/test/rfl/io/dump.rfl @@ -85,7 +85,7 @@ ;; window-join + window-join1 both compile to OP_WINDOW_JOIN. (set tr (table [s tm p] (list ['a 'a] [10:00:01.000 10:00:05.000] [100 200]))) (set qt (table [s tm b] (list ['a 'a 'a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101]))) -(set iv (map-left + [-2000 2000] (at tr 'tm))) +(set iv (map-right + [-2000 2000] (at tr 'tm))) (at (window-join [s tm] iv tr qt {mb: (min b)}) 'mb) -- [99 101] (at (window-join1 [s tm] iv tr qt {mb: (min b)}) 'mb) -- [99 101] diff --git a/test/rfl/ops/exec_coverage.rfl b/test/rfl/ops/exec_coverage.rfl index a3bdea9d..3c95ff24 100644 --- a/test/rfl/ops/exec_coverage.rfl +++ b/test/rfl/ops/exec_coverage.rfl @@ -122,7 +122,7 @@ ;; ==================================================================== (set tr (table [Sym Time Price] (list ['a 'a 'a 'b] [10:00:01.000 10:00:05.000 10:00:09.000 10:00:01.000] [100 200 300 400]))) (set qu (table [Sym Time Bid] (list ['a 'a 'a 'b 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000] [99 100 101 199 200]))) -(set iv (map-left + [-2000 2000] (at tr 'Time))) +(set iv (map-right + [-2000 2000] (at tr 'Time))) (count (at (window-join [Sym Time] iv tr qu {m: (min Bid)}) 'm)) -- 4 ;; ==================================================================== diff --git a/test/rfl/ops/opt_advanced.rfl b/test/rfl/ops/opt_advanced.rfl index 1116ff9b..cc5183df 100644 --- a/test/rfl/ops/opt_advanced.rfl +++ b/test/rfl/ops/opt_advanced.rfl @@ -295,7 +295,7 @@ ;; Every TR1 row gets at least one matching TQ1 row in its window. (set TR1 (table [Sym Time Price] (list ['a 'a 'b] [10:00:01.000 10:00:05.000 10:00:01.000] [100 200 400]))) (set TQ1 (table [Sym Time Bid] (list ['a 'a 'a 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000] [99 100 101 199]))) -(set IV (map-left + [-2000 2000] (at TR1 'Time))) +(set IV (map-right + [-2000 2000] (at TR1 'Time))) (count (at (window-join [Sym Time] IV TR1 TQ1 {m: (min Bid)}) 'm)) -- 3 ;; ==================================================================== diff --git a/test/rfl/ops/opt_branch_cov.rfl b/test/rfl/ops/opt_branch_cov.rfl index 5eba1b19..30ae65d3 100644 --- a/test/rfl/ops/opt_branch_cov.rfl +++ b/test/rfl/ops/opt_branch_cov.rfl @@ -378,7 +378,7 @@ ;; ════════════════════════════════════════════════════════════════════════ (set TWJ_T (table [Sym Time Price] (list ['a 'a 'b 'b] [10:00:01.000 10:00:05.000 10:00:01.000 10:00:05.000] [100 200 400 500]))) (set TWJ_Q (table [Sym Time Bid] (list ['a 'a 'b 'b] [10:00:00.000 10:00:04.000 10:00:00.000 10:00:04.000] [99 101 399 501]))) -(set TWJ_IV (map-left + [-2000 2000] (at TWJ_T 'Time))) +(set TWJ_IV (map-right + [-2000 2000] (at TWJ_T 'Time))) (count (at (window-join [Sym Time] TWJ_IV TWJ_T TWJ_Q {m: (min Bid)}) 'm)) -- 4 ;; ════════════════════════════════════════════════════════════════════════ diff --git a/test/rfl/ops/query_coverage.rfl b/test/rfl/ops/query_coverage.rfl index 48837497..2092b2c7 100644 --- a/test/rfl/ops/query_coverage.rfl +++ b/test/rfl/ops/query_coverage.rfl @@ -560,7 +560,7 @@ (set wjl (table [Sym Time] (list ['a 'a] [10:00:01.000 10:00:05.000]))) (set wjr (table [Sym Time Price] (list ['a 'a 'a] [10:00:00.000 10:00:02.000 10:00:04.000] (as 'F64 [99.5 100.5 101.5])))) -(set wjiv (map-left + [-2000 2000] (at wjl 'Time))) +(set wjiv (map-right + [-2000 2000] (at wjl 'Time))) ;; F64 sum: row0 interval [09:59:59,10:00:03] → prices 99.5+100.5=200.0 ;; row1 interval [10:00:03,10:00:07] → price 101.5 only (at (window-join [Sym Time] wjiv wjl wjr {total: (sum Price)}) 'total) -- [200.0 101.5] @@ -633,7 +633,7 @@ (set wjl2 (table [Sym Time] (list ['a 'a] [10:00:01.000 10:00:05.000]))) (set wjr2 (table [Sym Time Price] (list ['a 'a 'a] [10:00:00.000 10:00:02.000 10:00:04.000] (as 'F64 [99.5 100.5 101.5])))) -(set wjiv2 (map-left + [-2000 2000] (at wjl2 'Time))) +(set wjiv2 (map-right + [-2000 2000] (at wjl2 'Time))) (at (window-join [Sym Time] wjiv2 wjl2 wjr2 {f: (first Price)}) 'f) -- [99.5 101.5] (at (window-join [Sym Time] wjiv2 wjl2 wjr2 {l: (last Price)}) 'l) -- [100.5 101.5] diff --git a/test/rfl/query/query_branch_cov.rfl b/test/rfl/query/query_branch_cov.rfl index ad005ece..1e9add05 100644 --- a/test/rfl/query/query_branch_cov.rfl +++ b/test/rfl/query/query_branch_cov.rfl @@ -828,7 +828,7 @@ (set wjR (table [Sym Time Bid] (list ['x 'x 'y 'y] [10:00:00.500 10:00:02.500 10:00:01.500 10:00:02.500] [99.0 101.0 199.0 201.0]))) ;; intervals = per-left-row [lo hi] time window (in nanoseconds offset ;; via map-left + to each left Time). -(set wjIv (map-left + [-2000 2000] (at wjL 'Time))) +(set wjIv (map-right + [-2000 2000] (at wjL 'Time))) ;; Window [-2000ns, +2000ns] around each left Time: ;; (x, 10:00:01) ∩ window 09:59:59..10:00:03 — x-bids @00.5, @02.5 → {99, 101} ;; (y, 10:00:02) ∩ window 10:00:00..10:00:04 — y-bids @01.5, @02.5 → {199, 201} diff --git a/test/rfl/query/query_update_coverage.rfl b/test/rfl/query/query_update_coverage.rfl index aabfaba2..0c82e74e 100644 --- a/test/rfl/query/query_update_coverage.rfl +++ b/test/rfl/query/query_update_coverage.rfl @@ -360,7 +360,7 @@ ;; ──────────────────────────────────────────────────────────────────── (set wjt_null (table [Sym Time] (list ['a] [10:00:03.000]))) (set wjq_f64null (table [Sym Time Price] (list ['a 'a 'a 'a] [10:00:00.000 10:00:01.000 10:00:02.000 10:00:04.000] [0Nf 2.0 0Nf 4.0]))) -(set wji_null (map-left + [-3000 3000] (at wjt_null 'Time))) +(set wji_null (map-right + [-3000 3000] (at wjt_null 'Time))) ;; F64 null sum (line 10059): nn != NULL → sum skips nulls → 2+4=6 (at (window-join [Sym Time] wji_null wjt_null wjq_f64null {s: (sum Price)}) 's) -- [6.0] @@ -433,7 +433,7 @@ ;; ──────────────────────────────────────────────────────────────────── (set wjt_i32 (table [Sym Time] (list ['a] [10:00:01.000]))) (set wjq_i32 (table [Sym Time Price] (list ['a 'a] [10:00:00.000 10:00:02.000] (as 'I32 [10 20])))) -(set wji_i32 (map-left + [-2000 2000] (at wjt_i32 'Time))) +(set wji_i32 (map-right + [-2000 2000] (at wjt_i32 'Time))) ;; I32 result type (lines 10273-10274): first/max on I32 col → I32 output ;; Element at [0] of result is I32 atom 10i (first Price = 10i, max Price = 20i) @@ -447,7 +447,7 @@ ;; I64 null var/stddev (lines 10140-10141): null in I64 col + var → nn != NULL (set wjt_nullv (table [Sym Time] (list ['a] [10:00:03.000]))) (set wjq_i64nv (table [Sym Time Price] (list ['a 'a 'a 'a] [10:00:00.000 10:00:01.000 10:00:02.000 10:00:04.000] [0Nl 2 0Nl 4]))) -(set wji_nullv (map-left + [-3000 3000] (at wjt_nullv 'Time))) +(set wji_nullv (map-right + [-3000 3000] (at wjt_nullv 'Time))) ;; var of non-null [2, 4] = 2.0 (sample variance) (at (window-join [Sym Time] wji_nullv wjt_nullv wjq_i64nv {v: (var Price)}) 'v) -- [2.0] ;; stddev of non-null [2, 4] = sqrt(2.0) ≈ 1.41 diff --git a/test/rfl/table/update.rfl b/test/rfl/table/update.rfl index 88dfff66..d8264c6d 100644 --- a/test/rfl/table/update.rfl +++ b/test/rfl/table/update.rfl @@ -173,7 +173,7 @@ t -- (table [ID Name Value] (list [1 2] [alice bob] [10.0 20.0])) (insert (table [a] (list (as 'Date (list)))) (list 1)) !- type ;; Typeless empty () column: adopts the type of the first inserted value -;; (q-style () column) instead of defaulting to I64 and rejecting non-ints. +;; instead of defaulting to I64 and rejecting non-ints. (insert (table [a] (list (list))) (list 3.14)) -- (table [a] (list [3.14])) (insert (table [a] (list (list))) (list 'x)) -- (table [a] (list ['x])) (insert (table [a] (list (list))) (list "s")) -- (table [a] (list (list "s")))