Repository: tweag/nickel
Pull request #2131 (2024-12-23 16:00:35)

Freeze records on insert/remove/update.
This finishes off #1877, I think.

I didn't do freezing for `map`, because I couldn't find an example where it made a difference. Did I miss something?

github-actions[bot]: <h2><a href="https://bencher.dev/perf/nickel/reports/68fb6bfa-c8c2-4b2e-8385-3c185fd3d118?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel"><img src="https://bencher.dev/favicon.svg" width="24" height="24" alt="🐰" /> Bencher Report</a></h2><table><tr><td>Branch</td><td><a href="https://bencher.dev/perf/nickel/branches/freeze-em?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">freeze-em</a></td></tr><tr><td>Testbed</td><td><a href="https://bencher.dev/perf/nickel/testbeds/ubuntu-latest?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">ubuntu-latest</a></td></tr></table><details><summary>Click to view all benchmark results</summary><br /><table><thead><tr><th>Benchmark</th><th><a href="https://bencher.dev/perf/nickel/measures/latency?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">Latency</a></th><th>nanoseconds (ns)</th></tr></thead><tbody><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/fibonacci-10?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">fibonacci 10</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=3cfaf19e-c9c5-4341-95cd-703456d0fc9f&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>490.24</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/foldl-arrays-50?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">foldl arrays 50</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=95048806-9bf2-49e4-88f2-dbcd66cbf68a&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>1,697.20</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/foldl-arrays-500?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">foldl arrays 500</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=15373c31-496e-4f43-bbb4-466ef49fdd87&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>6,795.90</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/foldr-strings-50?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">foldr strings 50</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=87746452-c4df-48ea-b5e9-8d66f82ca556&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>7,087.10</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/foldr-strings-500?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">foldr strings 500</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=7f141ea0-9fbb-4305-bb63-78f163e546d6&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>61,858.00</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/generate-normal-250?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">generate normal 250</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=8c154d79-dc68-4d93-abb3-83183a62f6a2&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>41,309.00</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/generate-normal-50?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">generate normal 50</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=6d0222f6-0b85-4eaf-8c5c-e8957c522ef9&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>1,927.00</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/generate-normal-unchecked-1000?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">generate normal unchecked 1000</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=be0d3f87-d089-4b81-af44-3bbc6e831697&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>3,321.00</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/generate-normal-unchecked-200?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">generate normal unchecked 200</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=2946df33-45e4-4282-b749-040582021fe3&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>763.86</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/pidigits-100?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">pidigits 100</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=5be25c57-d46f-4b48-b6f0-dee01b84220a&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>3,156.50</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/pipe-normal-20?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">pipe normal 20</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=7178a975-b548-4e78-8c84-4f3802668825&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>1,513.50</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/pipe-normal-200?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">pipe normal 200</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=6884f292-8796-44a8-a30c-d3cb2ed987f6&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>10,094.00</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/product-30?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">product 30</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=2f5f7f6a-4e54-4542-aa09-da5671953fbb&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>852.57</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/scalar-10?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">scalar 10</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=211bb66a-58f8-488b-97e4-7ea032870038&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>1,549.20</td></tr><tr><td><a href="https://bencher.dev/perf/nickel/benchmarks/sum-30?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">sum 30</a></td><td>📈 <a href="https://bencher.dev/perf/nickel?branches=c7996082-2b0b-49b0-85e5-5d8d1ed178b9&heads=5dd16f8a-aa71-4abf-9d75-8c8f2b1b327a&testbeds=66263ed0-9556-4385-9f30-26466ee26211&benchmarks=3f1710c5-ce75-4dfb-aadb-1435fed8a093&measures=2b5b9371-696f-422d-b1a5-262ce8157b6b&start_time=1732378480000&end_time=1734970480000&report=68fb6bfa-c8c2-4b2e-8385-3c185fd3d118">view plot</a><br />🚷 <a href="https://bencher.dev/perf/nickel/thresholds/ba0c38ae-36ba-42e6-9adc-b63d88dd463a?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">view threshold</a></td><td>829.84</td></tr></tbody></table></details><a href="https://bencher.dev/perf/nickel/reports/68fb6bfa-c8c2-4b2e-8385-3c185fd3d118?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr%2Bcomments&utm_term=nickel">🐰 View full continuous benchmarking report in Bencher</a><div id="bencher.dev/projects/nickel/id/freeze-em/ubuntu-latest/rust_criterion"></div>
jneem: I like the idea of a sharper distinction between records and dictionaries. But yes, I'd like this to go in before we figure that out :smile: 

Patch:
diff --git a/core/stdlib/std.ncl b/core/stdlib/std.ncl
index 91086278..ed5c5af9 100644
--- a/core/stdlib/std.ncl
+++ b/core/stdlib/std.ncl
@@ -3187,7 +3187,7 @@
           # => error
           ```
         "%%
-      = fun field content r => %record/insert% field r content,
+      = fun field content r => %record/insert% field (%record/freeze% r) content,
 
     insert_with_opts
       : forall a. String -> a -> { _ : a } -> { _ : a }
@@ -3221,7 +3221,7 @@
           # => error
           ```
         "%%
-      = fun field content r => %record/insert_with_opts% field r content,
+      = fun field content r => %record/insert_with_opts% field (%record/freeze% r) content,
 
     remove
       : forall a. String -> { _ : a } -> { _ : a }
@@ -3256,7 +3256,7 @@
           # => error
           ```
         "%
-      = fun field r => %record/remove% field r,
+      = fun field r => %record/remove% field (%record/freeze% r),
 
     remove_with_opts
       : forall a. String -> { _ : a } -> { _ : a }
@@ -3284,7 +3284,7 @@
           # => error
           ```
         "%
-      = fun field r => %record/remove_with_opts% field r,
+      = fun field r => %record/remove_with_opts% field (%record/freeze% r),
 
     update
       : forall a. String -> a -> { _ : a } -> { _ : a }
@@ -3327,6 +3327,7 @@
           ```
         "%
       = fun field content r =>
+        let r = %record/freeze% r in
         let r =
           if %record/has_field% field r then
             %record/remove% field r


Test patch:
diff --git a/core/tests/integration/inputs/records/freeze_insert_fails_on_missing.ncl b/core/tests/integration/inputs/records/freeze_insert_fails_on_missing.ncl
new file mode 100644
index 00000000..22dfaa4a
--- /dev/null
+++ b/core/tests/integration/inputs/records/freeze_insert_fails_on_missing.ncl
@@ -0,0 +1,8 @@
+# test.type = 'error'
+#
+# [test.metadata]
+# error = 'EvalError::MissingFieldDef'
+#
+# [test.metadata.expectation]
+# field = 'bar'
+((std.record.insert "bar" 1 { foo = bar + 1, bar | optional }) & { bar | force = 2 }).foo
diff --git a/core/tests/integration/inputs/records/freezing_ops.ncl b/core/tests/integration/inputs/records/freezing_ops.ncl
new file mode 100644
index 00000000..b289ad6e
--- /dev/null
+++ b/core/tests/integration/inputs/records/freezing_ops.ncl
@@ -0,0 +1,15 @@
+# test.type = 'pass'
+[
+  ((std.record.insert "baz" 1 { foo = bar + 1, bar = 0 }) & { bar | force = 2 }) == { foo = 1, bar = 2, baz = 1 },
+  ((std.record.insert_with_opts "baz" 1 { foo = bar + 1, bar = 0 }) & { bar | force = 2 }) == { foo = 1, bar = 2, baz = 1 },
+  (
+    (std.record.remove "to_remove" { foo = bar + 1, bar = 0, to_remove = false, rev_dep = to_remove })
+    & { bar | force = 1, to_remove = true }
+  ) == { foo = 1, bar = 1, to_remove = true, rev_dep = false },
+  (
+    (std.record.remove_with_opts "to_remove" { foo = bar + 1, bar = 0, to_remove = false, rev_dep = to_remove })
+    & { bar | force = 1, to_remove = true }
+  ) == { foo = 1, bar = 1, to_remove = true, rev_dep = false },
+  ((std.record.update "bar" 1 { foo = bar + 1, bar = 0 }) & { bar | force = 2 }) == { foo = 1, bar = 2, },
+]
+|> std.test.assert_all