beck_core/seq.rs
1//! A list's elements, in one of two layouts.
2//!
3//! [`docs/105-the-ecosystem-answer.md`](../../../../../docs/105-the-ecosystem-answer.md) §105.8:
4//!
5//! > `Value` is 16 bytes and a list is `List(Arc<Vec<Value>>)`, so a million doubles is a boxed
6//! > 16 MB; and `Float(u64)` is stored as an **order-preserving key** rather than as `f64` bits,
7//! > which is exactly right for the reason its doc comment gives — a map key and the state digest
8//! > need a total order agreeing with arithmetic — and exactly wrong for a dense kernel, which pays
9//! > a bit transform per operation. That is not a defect to fix in `Value`. It is a **second
10//! > representation to add**.
11//!
12//! This is that representation, and the sentence that matters most about it is the one it is
13//! **not**: it is not a second kind of list. A Beck program has one list type, one order, one
14//! equality, one digest and one wire format, and this module's whole obligation is that a caller
15//! cannot tell which layout it got.
16//!
17//! # The two layouts
18//!
19//! | Layout | Bytes an element | What it is for |
20//! |---|---|---|
21//! | [`Seq::Boxed`] | 16 | Anything at all — records, strings, nested lists. What every list has always been |
22//! | [`Seq::Ints`] | 8 | A list of `Int`, dense |
23//! | [`Seq::Floats`] | 8 | A list of `Float`, dense **and as `f64`** rather than as [`Value::Float`]'s order key |
24//!
25//! The `Floats` row is the one with a consequence beyond memory. A kernel — BLAS, an FFT, anything
26//! this project has no business reimplementing ([`105`](../../../../../docs/105-the-ecosystem-answer.md)
27//! §105.8) — takes a `*const f64`, and so does Apache Arrow: a `Float64Array`'s values buffer *is*
28//! a contiguous `f64` run. [`Seq::floats`] is that pointer, and until it existed there was nothing
29//! in this language to hand either of them.
30//!
31//! # What a caller may not be able to tell, stated as the four things
32//!
33//! Two `Seq`s holding the same elements are **the same list**, and four separate mechanisms have to
34//! agree about that or replay determinism ([`04`](../../../../../docs/04-compiler-architecture.md)
35//! §4.8) fails in a way that depends on how a value happened to be built:
36//!
37//! 1. **Equality and order.** `Ord` and `Eq` are written by hand here, over the logical sequence.
38//! A derived one would compare the *variant tag* first, making `Ints([1])` and `Boxed([Int(1)])`
39//! two different values and sorting every column before every list.
40//! 2. **The digest** ([`crate::core::digest`]) hashes a tag, the length and each element, and
41//! reaches the elements through [`Seq::iter`] — so it is the same bytes either way by
42//! construction rather than by a second implementation agreeing.
43//! 3. **The wire format** ([`crate::repr`]) does the same.
44//! 4. **`Value`'s size.** The layout is an enum *behind* the `Arc`, so a `Value` is still 16 bytes
45//! and a list still costs one pointer. Putting the enum in the `Value` would have widened every
46//! value in the language to pay for a representation most of them do not use, which is the
47//! trade [`crate::core::Value`]'s own doc comment refused for [`crate::core::Record`].
48//!
49//! `seq.rs`'s own tests assert the first, and `beck-cli/tests/records.rs` asserts all four against
50//! the layouts a program can actually produce.
51//!
52//! # Where a column comes from
53//!
54//! Nothing in the language says "make this a column", and nothing should: the layout is a fact
55//! about the elements, so it is chosen where a list is *built*. [`Seq::pack`] takes the elements a
56//! primitive produced and reads them; [`Seq::push`] promotes an **empty** list on its first element,
57//! which is what makes the accumulator idiom `go(i + 1, list_append(done, x))` — how `lib/`, the
58//! corpus and both SICP chapters build a list ([`70`](../../../../../docs/70-the-evaluator-gets-fast-report.md)
59//! §70.6) — produce a column with no program changing a line.
60//!
61//! Promotion is only ever `O(1)`: an empty list on its first push, or a `pack` over elements the
62//! caller had already built. A `Boxed` list of a million ints is **not** re-examined on every push,
63//! because that check is what would turn the idiom quadratic.
64//!
65//! # The off switch
66//!
67//! Choosing a layout is a choice the runtime makes unbidden, so [`docs/08`](../../../../../docs/08-roadmap.md)
68//! §8.3 item 8 applies: [`set_columns`] turns it off for the process, and the gate runs both
69//! settings. With it off every list is [`Seq::Boxed`] and every answer is the same one — which is
70//! what the switch is *for*, and what makes "a caller cannot tell" a test rather than a claim.
71//!
72//! Two callers reach it without recompiling: `beck_rt::AppConfig::columns` for a served
73//! application, and `BECK_COLUMNS=0` for a `beck` process that is not one — `run`, `test`, `bench`
74//! and `build` all build lists, and none of them has an `AppConfig`.
75//!
76//! It is **process-wide**, which is the one thing about it worth knowing before using it: a list is
77//! built in a hundred places that have no configuration in scope, and a `Value` may not carry one
78//! — it is 16 bytes on purpose. So two applications in one process share the setting, and a test
79//! binary that flips it has to serialise the tests that do.
80
81use std::cmp::Ordering;
82use std::sync::atomic::{AtomicBool, AtomicU64, Ordering as Atomic};
83
84use crate::core::Value;
85
86/// Whether a list may be stored as a column.
87///
88/// A process-wide switch rather than a parameter because a list is built in a hundred places that
89/// have no configuration in scope, and a `Value` may not carry one — it is 16 bytes on purpose.
90/// Read once per list *built*, never per element.
91static COLUMNS: AtomicBool = AtomicBool::new(true);
92
93/// Turn the columnar layout on or off for this process, and answer what it was.
94///
95/// [`docs/08`](../../../../../docs/08-roadmap.md) §8.3 item 8's off switch. Nothing observable
96/// changes: with it off every list is [`Seq::Boxed`], every answer is the same answer, and the
97/// difference is memory. That is exactly why it is worth having — the switched-off path is what a
98/// gate compares against, so "the two layouts are one list" is measured rather than asserted.
99pub fn set_columns(on: bool) -> bool {
100 COLUMNS.swap(on, Atomic::Relaxed)
101}
102
103/// Whether the columnar layout is on.
104pub fn columns() -> bool {
105 COLUMNS.load(Atomic::Relaxed)
106}
107
108/// How many columns this process has built.
109///
110/// [`docs/08`](../../../../../docs/08-roadmap.md) §8.3 item 9's half of the same obligation as the
111/// switch above: a choice made unbidden should be answerable after the fact. This is the smallest
112/// honest answer — not *which* list, but whether the layout is reaching anything at all — and it is
113/// what lets a sweep say "the corpus builds none" rather than leaving that to be assumed.
114///
115/// Counted where a column is *created*: a `pack` that found one, or a `push` that promoted an empty
116/// list. Not per element, and never on the boxed path.
117pub fn built() -> u64 {
118 BUILT.load(Atomic::Relaxed)
119}
120
121static BUILT: AtomicU64 = AtomicU64::new(0);
122
123fn note() {
124 BUILT.fetch_add(1, Atomic::Relaxed);
125}
126
127/// A list's elements. See the module docs for what a caller may not be able to tell.
128#[derive(Clone, Debug)]
129pub enum Seq {
130 /// Any elements at all.
131 Boxed(Vec<Value>),
132 /// `Int` elements, dense.
133 Ints(Vec<i64>),
134 /// `Float` elements, dense and as `f64`.
135 Floats(Vec<f64>),
136}
137
138impl Default for Seq {
139 fn default() -> Seq {
140 Seq::Boxed(Vec::new())
141 }
142}
143
144impl Seq {
145 /// The elements a primitive produced, in whatever layout they fit.
146 ///
147 /// One pass over a list that has just been built by a pass over something else, so this is a
148 /// constant on a cost the caller was already paying — and it stops at the first element that
149 /// does not fit, so a list of records pays a single comparison.
150 pub fn pack(values: Vec<Value>) -> Seq {
151 if !columns() || values.is_empty() {
152 return Seq::Boxed(values);
153 }
154 // The first element decides which column to try, so a list of records — which is most of
155 // them — pays one `matches!` and nothing else. There is deliberately no length threshold:
156 // a threshold is a constant somebody would have to justify, and what it would buy is eight
157 // bytes on a list of one.
158 match &values[0] {
159 Value::Int(_) => {
160 let mut out = Vec::with_capacity(values.len());
161 for v in &values {
162 match v {
163 Value::Int(i) => out.push(*i),
164 _ => return Seq::Boxed(values),
165 }
166 }
167 note();
168 Seq::Ints(out)
169 }
170 Value::Float(_) => {
171 let mut out = Vec::with_capacity(values.len());
172 for v in &values {
173 match v.as_f64() {
174 Some(f) => out.push(f),
175 None => return Seq::Boxed(values),
176 }
177 }
178 note();
179 Seq::Floats(out)
180 }
181 _ => Seq::Boxed(values),
182 }
183 }
184
185 /// The elements, unpacked — what a caller that wants a `Vec<Value>` gets.
186 pub fn to_vec(&self) -> Vec<Value> {
187 match self {
188 Seq::Boxed(v) => v.clone(),
189 _ => self.iter().collect(),
190 }
191 }
192
193 pub fn len(&self) -> usize {
194 match self {
195 Seq::Boxed(v) => v.len(),
196 Seq::Ints(v) => v.len(),
197 Seq::Floats(v) => v.len(),
198 }
199 }
200
201 pub fn is_empty(&self) -> bool {
202 self.len() == 0
203 }
204
205 /// One element, as a [`Value`]. By value rather than by reference, because a column has no
206 /// `Value` to lend — and an `Int` or a `Float` is two words, so there is nothing to save.
207 pub fn get(&self, i: usize) -> Option<Value> {
208 match self {
209 Seq::Boxed(v) => v.get(i).cloned(),
210 Seq::Ints(v) => v.get(i).map(|&x| Value::Int(x)),
211 Seq::Floats(v) => v.get(i).map(|&x| Value::float(x)),
212 }
213 }
214
215 /// Every element, in order.
216 pub fn iter(&self) -> Iter<'_> {
217 Iter {
218 seq: self,
219 at: 0,
220 end: self.len(),
221 }
222 }
223
224 /// The elements as a dense `i64` run, if that is what this list is.
225 pub fn ints(&self) -> Option<&[i64]> {
226 match self {
227 Seq::Ints(v) => Some(v),
228 _ => None,
229 }
230 }
231
232 /// **The elements as a dense `f64` run**, if that is what this list is — the pointer a kernel
233 /// and an Arrow `Float64Array` both take, and the reason this module exists
234 /// ([`105`](../../../../../docs/105-the-ecosystem-answer.md) §105.8).
235 ///
236 /// `None` for a boxed list rather than a copy of one: a caller that gets a slice knows it cost
237 /// nothing, and a caller that gets `None` can decide whether a copy is worth it. Handing back a
238 /// materialised buffer here would make the cheap case and the expensive one look alike, which
239 /// is the shape of every "why is this slow" question a zero-copy interface exists to prevent.
240 pub fn floats(&self) -> Option<&[f64]> {
241 match self {
242 Seq::Floats(v) => Some(v),
243 _ => None,
244 }
245 }
246
247 /// Every element, **borrowed** where the layout has one to lend.
248 ///
249 /// [`Seq::iter`] yields by value, which is right for a caller that wanted an owned `Value` and
250 /// wrong for one that only wanted to look: cloning a `Value::Data` is an atomic increment and a
251 /// later decrement, and the digest, the wire format and `to_json` each walk every element of
252 /// every list without keeping any of them. Those walk through here, so the boxed layout — which
253 /// is every list that is not a column — pays exactly what it paid before this module existed.
254 ///
255 /// A column has no `Value` to lend, so one is built on the stack and lent; it is two words and
256 /// no allocation.
257 pub fn for_each(&self, mut f: impl FnMut(&Value)) {
258 match self {
259 Seq::Boxed(v) => v.iter().for_each(f),
260 _ => {
261 for i in 0..self.len() {
262 if let Some(x) = self.get(i) {
263 f(&x);
264 }
265 }
266 }
267 }
268 }
269
270 /// [`Seq::for_each`] for a walk that can fail, which is what a wire encoder is.
271 pub fn try_for_each<E>(&self, mut f: impl FnMut(&Value) -> Result<(), E>) -> Result<(), E> {
272 match self {
273 Seq::Boxed(v) => v.iter().try_for_each(f),
274 _ => {
275 for i in 0..self.len() {
276 if let Some(x) = self.get(i) {
277 f(&x)?;
278 }
279 }
280 Ok(())
281 }
282 }
283 }
284
285 /// The elements as a slice, borrowed when the layout allows and materialised when it does not.
286 pub fn as_values(&self) -> std::borrow::Cow<'_, [Value]> {
287 match self {
288 Seq::Boxed(v) => std::borrow::Cow::Borrowed(v),
289 _ => std::borrow::Cow::Owned(self.to_vec()),
290 }
291 }
292
293 /// The smallest element, and the largest — over the dense buffer where there is one.
294 ///
295 /// This is the half of [`105`](../../../../../docs/105-the-ecosystem-answer.md) §105.10's
296 /// aggregate row that costs nothing to take: `min` over an `Ints` column is a pass over `i64`s
297 /// with no `Value` built at all, where the boxed form builds one per element to compare it.
298 pub fn min(&self) -> Option<Value> {
299 match self {
300 Seq::Ints(v) => v.iter().min().map(|&x| Value::Int(x)),
301 _ => self.iter().min(),
302 }
303 }
304
305 pub fn max(&self) -> Option<Value> {
306 match self {
307 Seq::Ints(v) => v.iter().max().map(|&x| Value::Int(x)),
308 _ => self.iter().max(),
309 }
310 }
311
312 /// Whether this list is stored as a column — for a gate and for a report, never for a decision
313 /// about what a program means.
314 pub fn is_column(&self) -> bool {
315 !matches!(self, Seq::Boxed(_))
316 }
317
318 /// What this list occupies on the heap, in bytes, not counting anything its elements point at.
319 ///
320 /// The number the second layout exists to move, so it is readable rather than inferred.
321 pub fn heap_bytes(&self) -> usize {
322 match self {
323 Seq::Boxed(v) => v.capacity() * std::mem::size_of::<Value>(),
324 Seq::Ints(v) => v.capacity() * std::mem::size_of::<i64>(),
325 Seq::Floats(v) => v.capacity() * std::mem::size_of::<f64>(),
326 }
327 }
328
329 /// Add one element to the end.
330 ///
331 /// An **empty** list promotes to the layout its first element fits, which is what gives the
332 /// accumulator idiom a column; a list that already has elements keeps its layout, or falls back
333 /// to [`Seq::Boxed`] once for an element that does not fit. Both are `O(1)` amortised. What is
334 /// deliberately *not* here is a re-examination of a boxed list on every push: that would be the
335 /// quadratic [`70`](../../../../../docs/70-the-evaluator-gets-fast-report.md) removed from this
336 /// idiom, put straight back.
337 pub fn push(&mut self, value: Value) {
338 match (&mut *self, &value) {
339 (Seq::Ints(v), Value::Int(x)) => v.push(*x),
340 (Seq::Floats(v), Value::Float(_)) => v.push(value.as_f64().unwrap_or(0.0)),
341 (Seq::Boxed(v), Value::Int(x)) if v.is_empty() && columns() => {
342 note();
343 *self = Seq::Ints(vec![*x]);
344 }
345 (Seq::Boxed(v), Value::Float(_)) if v.is_empty() && columns() => {
346 note();
347 *self = Seq::Floats(vec![value.as_f64().unwrap_or(0.0)]);
348 }
349 (Seq::Boxed(v), _) => v.push(value),
350 // A column that has met an element it cannot hold. Once per list, and never again.
351 _ => {
352 let mut v = self.to_vec();
353 v.push(value);
354 *self = Seq::Boxed(v);
355 }
356 }
357 }
358
359 /// Add every element of another list to the end.
360 pub fn extend(&mut self, other: &Seq) {
361 match (&mut *self, other) {
362 (Seq::Ints(a), Seq::Ints(b)) => a.extend_from_slice(b),
363 (Seq::Floats(a), Seq::Floats(b)) => a.extend_from_slice(b),
364 (Seq::Boxed(a), Seq::Boxed(b)) if !a.is_empty() || !columns() => a.extend_from_slice(b),
365 _ => {
366 for v in other.iter() {
367 self.push(v);
368 }
369 }
370 }
371 }
372
373 /// A range of the elements, as a list of its own.
374 pub fn slice(&self, from: usize, to: usize) -> Seq {
375 let (from, to) = (from.min(self.len()), to.min(self.len()));
376 if from >= to {
377 return Seq::default();
378 }
379 match self {
380 Seq::Boxed(v) => Seq::Boxed(v[from..to].to_vec()),
381 Seq::Ints(v) => Seq::Ints(v[from..to].to_vec()),
382 Seq::Floats(v) => Seq::Floats(v[from..to].to_vec()),
383 }
384 }
385
386 /// Replace one element, keeping the layout where the new element fits it.
387 pub fn set(&mut self, i: usize, value: Value) {
388 match (&mut *self, &value) {
389 (Seq::Boxed(v), _) if i < v.len() => v[i] = value,
390 (Seq::Ints(v), Value::Int(x)) if i < v.len() => v[i] = *x,
391 (Seq::Floats(v), Value::Float(_)) if i < v.len() => {
392 v[i] = value.as_f64().unwrap_or(0.0)
393 }
394 _ => {
395 let mut v = self.to_vec();
396 if i < v.len() {
397 v[i] = value;
398 }
399 *self = Seq::Boxed(v);
400 }
401 }
402 }
403}
404
405impl From<Vec<Value>> for Seq {
406 fn from(v: Vec<Value>) -> Seq {
407 Seq::pack(v)
408 }
409}
410
411impl FromIterator<Value> for Seq {
412 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Seq {
413 Seq::pack(iter.into_iter().collect())
414 }
415}
416
417/// Every element of a [`Seq`], in order, as [`Value`]s.
418pub struct Iter<'a> {
419 seq: &'a Seq,
420 at: usize,
421 end: usize,
422}
423
424impl Iterator for Iter<'_> {
425 type Item = Value;
426
427 fn next(&mut self) -> Option<Value> {
428 if self.at >= self.end {
429 return None;
430 }
431 let out = self.seq.get(self.at);
432 self.at += 1;
433 out
434 }
435
436 fn size_hint(&self) -> (usize, Option<usize>) {
437 let left = self.end.saturating_sub(self.at);
438 (left, Some(left))
439 }
440}
441
442impl ExactSizeIterator for Iter<'_> {}
443
444impl DoubleEndedIterator for Iter<'_> {
445 fn next_back(&mut self) -> Option<Value> {
446 if self.at >= self.end {
447 return None;
448 }
449 self.end -= 1;
450 self.seq.get(self.end)
451 }
452}
453
454impl<'a> IntoIterator for &'a Seq {
455 type Item = Value;
456 type IntoIter = Iter<'a>;
457
458 fn into_iter(self) -> Iter<'a> {
459 self.iter()
460 }
461}
462
463// ---------------------------------------------------------------------------------------------
464// The four things a caller may not be able to tell
465// ---------------------------------------------------------------------------------------------
466//
467// Written by hand, and that is the point rather than an inconvenience. A derived `Ord` compares the
468// enum's discriminant first, so `Ints([1])` would sort before `Boxed([Int(1)])` — two values a
469// program cannot tell apart, ordered differently, in the order that reaches the rendered page and
470// the replay digest.
471
472impl PartialEq for Seq {
473 fn eq(&self, other: &Seq) -> bool {
474 match (self, other) {
475 (Seq::Ints(a), Seq::Ints(b)) => a == b,
476 (Seq::Boxed(a), Seq::Boxed(b)) => a == b,
477 // Two `Floats` columns included: `f64`'s own `==` says `NaN != NaN` and `-0.0 == 0.0`,
478 // and `Value::float` says the opposite of both on purpose.
479 _ => self.len() == other.len() && self.cmp(other).is_eq(),
480 }
481 }
482}
483
484impl Eq for Seq {}
485
486impl PartialOrd for Seq {
487 fn partial_cmp(&self, other: &Seq) -> Option<Ordering> {
488 Some(self.cmp(other))
489 }
490}
491
492impl Ord for Seq {
493 /// Lexicographic over the elements, which is what `Vec<Value>`'s derived order was.
494 ///
495 /// A `Floats` column is compared **through [`Value::float`]**, not as raw `f64`. The two agree
496 /// on every ordinary number because the order key is monotone, and disagree on exactly the two
497 /// IEEE values [`Value::float`] exists to canonicalise — `NaN`, which has no `partial_cmp`, and
498 /// `-0.0`, which compares equal to `0.0` and hashes differently. Going through the constructor
499 /// is how those two stay the one value the language says they are.
500 fn cmp(&self, other: &Seq) -> Ordering {
501 if let (Seq::Ints(a), Seq::Ints(b)) = (self, other) {
502 return a.cmp(b);
503 }
504 if let (Seq::Boxed(a), Seq::Boxed(b)) = (self, other) {
505 return a.cmp(b);
506 }
507 let mut left = self.iter();
508 let mut right = other.iter();
509 loop {
510 match (left.next(), right.next()) {
511 (None, None) => return Ordering::Equal,
512 (None, Some(_)) => return Ordering::Less,
513 (Some(_), None) => return Ordering::Greater,
514 (Some(a), Some(b)) => match a.cmp(&b) {
515 Ordering::Equal => continue,
516 other => return other,
517 },
518 }
519 }
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526
527 /// [`set_columns`] is process-wide, and `cargo test` runs a binary's tests on several threads.
528 /// Every test that flips it takes this first, so one test's `off` is not another's answer.
529 static SWITCH: std::sync::Mutex<()> = std::sync::Mutex::new(());
530
531 fn boxed(v: Vec<Value>) -> Seq {
532 Seq::Boxed(v)
533 }
534
535 /// The fourth thing a caller may not be able to tell: what a `Value` costs.
536 ///
537 /// The layout enum lives behind the `Arc`, so it is a word inside an allocation a list already
538 /// had. In the `Value` it would have widened **every** value in the language — an `Int`, a
539 /// `Bool`, a `Unit` — to pay for a representation none of them uses.
540 #[test]
541 fn a_value_is_still_two_words() {
542 assert_eq!(std::mem::size_of::<Value>(), 16);
543 }
544
545 #[test]
546 fn a_column_and_a_boxed_list_of_the_same_elements_are_one_value() {
547 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
548 let ints = Seq::pack(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
549 let same = boxed(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
550 assert!(
551 ints.is_column(),
552 "the elements fit a column and it did not take one"
553 );
554 assert!(!same.is_column());
555 assert_eq!(ints, same);
556 assert_eq!(ints.cmp(&same), Ordering::Equal);
557 assert_eq!(ints.to_vec(), same.to_vec());
558 assert_eq!(ints.iter().collect::<Vec<_>>(), same.to_vec());
559 }
560
561 /// The one that a derived `Ord` gets wrong, and it gets it wrong in the direction that reaches
562 /// a rendered page: every column would sort before every list.
563 #[test]
564 fn the_order_is_over_the_elements_and_not_over_the_layout() {
565 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
566 let column = Seq::pack(vec![Value::Int(5), Value::Int(6)]);
567 let smaller = boxed(vec![Value::Int(1), Value::Int(2)]);
568 assert!(
569 column > smaller,
570 "a column sorted below a smaller boxed list"
571 );
572 let longer = Seq::pack(vec![Value::Int(5), Value::Int(6), Value::Int(7)]);
573 assert!(longer > column, "a prefix did not sort below its extension");
574 }
575
576 /// `-0.0` and `NaN` are the two IEEE values `Value::float` canonicalises, and a raw `f64`
577 /// column holds them as themselves — so the comparison has to go back through the constructor.
578 #[test]
579 fn a_float_column_canonicalises_the_two_values_ieee_will_not() {
580 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
581 let column = Seq::pack(vec![Value::float(-0.0), Value::float(f64::NAN)]);
582 let same = boxed(vec![Value::float(0.0), Value::float(f64::NAN)]);
583 assert!(column.is_column());
584 assert_eq!(column, same);
585 // And the raw buffer really does hold the un-canonicalised bits, so the test above is not
586 // passing because the constructor already flattened them.
587 let raw = Seq::Floats(vec![-0.0, f64::NAN]);
588 assert_eq!(raw, same);
589 assert_eq!(raw.get(0), Some(Value::float(0.0)));
590 }
591
592 #[test]
593 fn an_accumulator_starting_from_nothing_becomes_a_column() {
594 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
595 let mut s = Seq::default();
596 for i in 0..8 {
597 s.push(Value::Int(i));
598 }
599 assert!(
600 s.is_column(),
601 "the accumulator idiom did not produce a column"
602 );
603 assert_eq!(s.len(), 8);
604 assert_eq!(s.floats(), None);
605 assert_eq!(s.ints().map(<[i64]>::len), Some(8));
606
607 // And one element that does not fit falls back once, keeping every element it had.
608 s.push(Value::str_("nine"));
609 assert!(!s.is_column());
610 assert_eq!(s.len(), 9);
611 assert_eq!(s.get(0), Some(Value::Int(0)));
612 assert_eq!(s.get(8), Some(Value::str_("nine")));
613 }
614
615 #[test]
616 fn the_switch_turns_it_off_and_changes_no_answer() {
617 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
618 let was = set_columns(false);
619 let off = Seq::pack(vec![Value::Int(1), Value::Int(2)]);
620 set_columns(true);
621 let on = Seq::pack(vec![Value::Int(1), Value::Int(2)]);
622 set_columns(was);
623 assert!(!off.is_column(), "the switch did not turn the layout off");
624 assert!(on.is_column());
625 assert_eq!(off, on);
626 assert_eq!(off.to_vec(), on.to_vec());
627 // The difference is the one thing it is allowed to be.
628 assert!(on.heap_bytes() < off.heap_bytes());
629 }
630
631 #[test]
632 fn a_mixed_list_stays_boxed_and_an_empty_one_has_no_layout_to_choose() {
633 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
634 assert!(!Seq::pack(vec![Value::Int(1), Value::str_("x")]).is_column());
635 assert!(!Seq::pack(vec![Value::str_("x"), Value::Int(1)]).is_column());
636 assert!(!Seq::pack(Vec::new()).is_column());
637 // No length threshold: one element is a column too, because the alternative is a constant
638 // whose whole benefit is eight bytes.
639 assert!(Seq::pack(vec![Value::Int(1)]).is_column());
640 }
641
642 /// `crate::delta` walks a list from both ends to find the shared prefix and suffix of two
643 /// versions, so the reversed iterator is on the path that decides what a patch says.
644 #[test]
645 fn walking_a_column_backwards_gives_the_elements_in_reverse() {
646 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
647 let s = Seq::pack((0..5).map(Value::Int).collect());
648 assert!(s.is_column());
649 let back: Vec<Value> = s.iter().rev().collect();
650 assert_eq!(back, (0..5).rev().map(Value::Int).collect::<Vec<_>>());
651 // And the two ends meet without overlapping, which is the property a shared-prefix and
652 // shared-suffix walk over one iterator would otherwise get wrong.
653 let mut it = s.iter();
654 assert_eq!(it.next(), Some(Value::Int(0)));
655 assert_eq!(it.next_back(), Some(Value::Int(4)));
656 assert_eq!(it.len(), 3);
657 assert_eq!(
658 it.collect::<Vec<_>>(),
659 vec![Value::Int(1), Value::Int(2), Value::Int(3)]
660 );
661 }
662
663 #[test]
664 fn a_slice_of_a_column_is_a_column_and_holds_what_it_should() {
665 let _held = SWITCH.lock().unwrap_or_else(|e| e.into_inner());
666 let s = Seq::pack((0..10).map(Value::Int).collect());
667 let mid = s.slice(2, 5);
668 assert!(mid.is_column());
669 assert_eq!(
670 mid.to_vec(),
671 vec![Value::Int(2), Value::Int(3), Value::Int(4)]
672 );
673 assert!(s.slice(5, 5).is_empty());
674 assert_eq!(s.slice(8, 100).len(), 2);
675 }
676}