diff --git a/lib/kernel/runtime.sx b/lib/kernel/runtime.sx index 6c5be95a..06b531bc 100644 --- a/lib/kernel/runtime.sx +++ b/lib/kernel/runtime.sx @@ -559,6 +559,28 @@ (kernel-combine fn-val (list acc (first xs)) dyn-env) dyn-env))))) +;; (apply COMBINER ARGS-LIST) — call COMBINER with the elements of +;; ARGS-LIST as arguments. The Kernel canonical use: turn a constructed +;; list of values into a function call. We skip the applicative's +;; auto-eval step (via unwrap) because ARGS-LIST is already values, not +;; expressions; for a bare operative, we pass through directly. +(define kernel-apply-applicative + (kernel-make-primitive-applicative-with-env + (fn (args dyn-env) + (cond + ((not (= (length args) 2)) + (error "apply: expects (combiner args-list)")) + ((not (kernel-combiner? (first args))) + (error "apply: first arg must be a combiner")) + ((not (list? (nth args 1))) + (error "apply: second arg must be a list")) + (:else + (let ((op (cond + ((kernel-applicative? (first args)) + (kernel-unwrap (first args))) + (:else (first args))))) + (kernel-combine op (nth args 1) dyn-env))))))) + (define kernel-reduce-applicative (kernel-make-primitive-applicative-with-env (fn (args dyn-env) @@ -794,6 +816,7 @@ (kernel-env-bind! env "map" kernel-map-applicative) (kernel-env-bind! env "filter" kernel-filter-applicative) (kernel-env-bind! env "reduce" kernel-reduce-applicative) + (kernel-env-bind! env "apply" kernel-apply-applicative) (kernel-env-bind! env "not" kernel-not-applicative) (kernel-env-bind! env "make-encapsulation-type" kernel-make-encap-type-applicative) diff --git a/lib/kernel/tests/standard.sx b/lib/kernel/tests/standard.sx index af112bba..0569c77d 100644 --- a/lib/kernel/tests/standard.sx +++ b/lib/kernel/tests/standard.sx @@ -395,4 +395,24 @@ (ks-eval "(reduce ($lambda (acc x) (cons x acc)) () (list 1 2 3))") (list 3 2 1)) +;; ── apply ──────────────────────────────────────────────────────── +(ks-test "apply: + over list" + (ks-eval "(apply + (list 1 2 3 4 5))") 15) +(ks-test "apply: lambda" + (ks-eval "(apply ($lambda (a b c) (* a (+ b c))) (list 2 3 4))") 14) +(ks-test "apply: list identity" + (ks-eval "(apply list (list 1 2 3))") (list 1 2 3)) +(ks-test "apply: empty args list" + (ks-eval "(apply + (list))") 0) +(ks-test "apply: single arg list" + (ks-eval "(apply ($lambda (x) (* x 10)) (list 7))") 70) +(ks-test "apply: built via map+apply" + ;; (apply + (map ($lambda (x) (* x x)) (list 1 2 3))) → 1+4+9 = 14 + (ks-eval + "(apply + (map ($lambda (x) (* x x)) (list 1 2 3)))") 14) +(ks-test "apply: error on non-list args" + (guard (e (true :raised)) + (ks-eval "(apply + 5)")) + :raised) + (define ks-tests-run! (fn () {:total (+ ks-test-pass ks-test-fail) :passed ks-test-pass :failed ks-test-fail :fails ks-test-fails})) diff --git a/plans/kernel-on-sx.md b/plans/kernel-on-sx.md index be8513df..67bc2cf7 100644 --- a/plans/kernel-on-sx.md +++ b/plans/kernel-on-sx.md @@ -161,6 +161,7 @@ The motivation is that SX's host `make-env` family is registered only in HTTP/si ## Progress log +- 2026-05-11 — `apply` combinator. `(apply F (list V1 V2 V3))` ≡ `(F V1 V2 V3)` but with the argument list constructed at runtime. Implementation: unwrap an applicative F to its underlying operative, then `kernel-combine` it with the values — skipping the auto-eval pass since args are already values. For a bare operative F, pass through directly. 7 new tests. chisel: shapes-reflective. The unwrap-then-combine pattern is universal across reflective Lisps and should be in the `combiner.sx` API alongside the existing wrap/unwrap pair: `refl-apply F ARGS DYN-ENV` is the third API entry needed for higher-order composition. 296 tests total. - 2026-05-11 — `map` / `filter` / `reduce` list combinators. Required adding `kernel-make-primitive-applicative-with-env` to `eval.sx`: standard primitive applicatives drop dyn-env, but combinators that re-enter the evaluator (calling user-supplied functions on each element) need it. The three combinators use `kernel-combine` directly with the captured dyn-env. 10 new tests covering map/filter/reduce on numbers, empty lists, closures, and list construction. chisel: shapes-reflective. The "primitive applicatives split into two flavours — env-blind and env-aware" finding goes into the proposed `lib/guest/reflective/combiner.sx` API. Every reflective Lisp must distinguish "I just need values" from "I need to re-enter evaluation" — the with-env constructor pair is universal. 289 tests total. - 2026-05-11 — Variadic `+ - * /` and chained `< > <=? >=?`. `(+ 1 2 3)` = 6, `(+)` = 0, `(+ 7)` = 7. `(- 10 1 2 3)` = 4 (left fold); single-arg `-` negates. `(* 1 2 3 4)` = 24, `(*)` = 1. Chained comparison: `(< 1 2 3)` ≡ `(< 1 2) ∧ (< 2 3)`. Implementation: `knl-fold-app` for n-ary fold with zero-arity identity and one-arity special-case; `knl-chain-cmp` for chained boolean. 19 new tests. chisel: nothing (mechanical extension of existing arithmetic primitives). 279 tests total. - 2026-05-11 — `$let*` sequential let. Each binding evaluated in scope where earlier bindings are visible, so `($let* ((x 1) (y (+ x 1))) y)` returns 2. Implemented by nesting envs one per binding — `knl-let*-step` recursively builds the env chain. `$let` and `$let*` now both accept multi-expression bodies (`knl-eval-body` re-used). 8 new tests in `tests/hygiene.sx`. chisel: nothing (a standard derived form). 260 tests total.