From eb0cd5bc78569c8e8347afb52dd5bb7d3d96d513 Mon Sep 17 00:00:00 2001 From: mikera Date: Sat, 21 Sep 2024 09:56:53 +0100 Subject: [PATCH] Add core `update` function and tests --- convex-core/src/main/cvx/convex/core/core.cvx | 33 ++++++++++++------- .../test/java/convex/core/lang/CoreTest.java | 23 +++++++++++++ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/convex-core/src/main/cvx/convex/core/core.cvx b/convex-core/src/main/cvx/convex/core/core.cvx index b86bafe38..a7fbb2317 100644 --- a/convex-core/src/main/cvx/convex/core/core.cvx +++ b/convex-core/src/main/cvx/convex/core/core.cvx @@ -646,20 +646,18 @@ (compile (quote ~code)))) (defmacro tailcall - ^{:doc {:description ["Advanced feature. While `return` stops the execution of a function and return, `tailcall` calls another one without consuming additional stack depth." - "Rest of the current function will never be executed."] + ^{:doc {:description ["Perform a tail call to a function without consuming additional stack depth." + "Assumes tail position: rest of the current function, if any, will not be executed."] :examples [{:code "(tailcall (some-function 1 2 3))"}] :signature [{:params [[f & args]] }]}} [callspec] - (let [] - (when-not (list? callspec) - (fail :ARGUMENT - "tailcall requires a list representing function invocation")) - (let [n (count callspec)] - (if (== n 0) - (fail :ARGUMENT "Tailcall requires at least a function argument in call list")) - (cons 'tailcall* - callspec)))) + (cond + (not (list? callspec)) + (fail :ARGUMENT "tailcall requires a list representing function invocation") + (empty? callspec) (fail :ARGUMENT "tailcall requires a function argument") + (cons + 'tailcall* + callspec))) (defmacro undef ^{:doc {:description "Opposite of `def`. Undefines a symbol, removing the mapping from the current environment if it exists." @@ -678,3 +676,16 @@ (let [new-value# ~(cons 'do body)] (recur ~change new-value#)) value#))) + + (defn update + ^{:doc {:description "Update a value in a associative data structure by applying a function." + :examples [{:code "(update {:count 1} :count inc)"}] + :signature [{:params [m k f & args]}]}} + ([m k f] + (assoc m k (f (get m k)))) + + ([m k f x] + (assoc m k (f (get m k) x))) + + ([m k f x & more] + (assoc m k (apply f (get m k) more)))) diff --git a/convex-core/src/test/java/convex/core/lang/CoreTest.java b/convex-core/src/test/java/convex/core/lang/CoreTest.java index 39bbab2f5..8671c07e4 100644 --- a/convex-core/src/test/java/convex/core/lang/CoreTest.java +++ b/convex-core/src/test/java/convex/core/lang/CoreTest.java @@ -1322,6 +1322,29 @@ public void testAssocSets() { assertArityError(step("(assoc #{} 1 2 3)")); // arity before cast assertArityError(step("(assoc #{} 1)")); } + + @Test + public void testUpdate() { + assertEquals(Vectors.of(1,2,4),eval("(update [1 2 3] 2 inc)")); + assertEquals(Vectors.of(3),eval("(update [[1 2 3]] 0 count)")); + + assertEquals(Vectors.of(1,2,3),eval("(update [1 2 3] 1 identity)")); + + // nil works as empty map + assertEquals(Maps.of(2,Sets.of(2,3)),eval("(update nil 2 union #{2,3})")); + + assertEquals(2L, evalL("(:count (update {:count 1} :count inc))")); // Example from docstring + + // 666 is a bad value in all cases + assertCastError(step("(update [1 2 3] 2 666)")); + assertCastError(step("(update 666 2 inc)")); + + assertArityError(step("(update)")); + assertArityError(step("(update {} :k)")); + + // arity error on count + assertArityError(step("(update [[2]] 0 count 666)")); + } @Test public void testAssocIn() {