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}