flexseq

knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
if(requireNamespace("pkgload", quietly = TRUE)) {
  pkgload::load_all(".", quiet = TRUE)
} else if(requireNamespace("Immutables", quietly = TRUE)) {
  library(Immutables)
} else {
  stop("Need either installed 'Immutables' or the 'pkgload' package to render this vignette.")
}

flexseq basics

flexseq is a persistent sequence type, with many of the same features as an R list(), including storing arbitrary items. All updates return a new object and keep the original unchanged.

x <- flexseq(1, 2, 3)
x

x2 <- as_flexseq(letters[1:5])
x2

Names and indexing

Flexseqs may be named, but if any are named they must all have unique non-NULL names. Large named flexseqs are somewhat slower than unnamed ones; for efficient key/value stores consider using an ordered_sequence().

x <- flexseq(a = 1, b = 2, c = 3)
x
x[c("c", "b")]

x$b <- NULL # delete b
x

In general indexing and slicing works as it does with lists: [] returns a sublist indexed by integer (by position), character vector (by name if named), or logical vector (by inclusion). Note that these operations are $O(k\log n)$ where $k$ is the length of the indexing vector.

x <- as_flexseq(letters[1:6])

x[[3]]

x[c(1, 3, 5)]

End operations

Flexseqs support pushing new elements onto the front or back of the sequences, peeking at the front or back (i.e., getting a copy of the first or last item), and popping from the front or back, which returns the removed element and a copy of the flexseq input with that item removed. pop_front() and pop_back() both return a list with fields $value and $remaining; peek_back() is the symmetric counterpart to peek_front().

x <- as_flexseq(4:6)

x <- x |>
  push_back(100) |>
  push_front(50)

x

xpopped <- pop_front(x)

xpopped$value
xpopped$remaining

Positional operations

Beyond the ends, flexseqs support arbitrary-position persistent operations. peek_at() is the non-throwing positional read (compare with [[, which errors when out of bounds); pop_at() removes an element at a given index and returns $value/$remaining; insert_at() inserts before a given index, or at length(x) + 1 to append.

x <- flexseq("a", "b", "c", "d")

peek_at(x, 10)  # NULL, no error

out <- pop_at(x, 2)
out$value
out$remaining

insert_at(x, 3, c("x", "y"))

Transforming and combining

c() concatenates flexseqs, and fapply() applies a function to each element of one returning the result as a flexseq, analygous to lapply() for R lists.

x <- as_flexseq(4:6)
x2 <- as_flexseq(8:10)

c(x, x2)

x <- as_flexseq(1:3)
fapply(x, function(el) el * 10)

merge(x, y) is provided as an alias for c(x, y) so that merge() works uniformly across the package's four structure types. For flexseq, c() and merge() are equivalent; the distinction matters for ordered_sequence, interval_index, and priority_queue, where merge() performs a proper sorted/priority-aware combine.

loop() (re-exported from the coro package) enables for-loop traversal, yielding elements left-to-right lazily without materializing a list.

x <- flexseq("a", "b", "c", "d")

loop(for (el in x) {
  print(el)
})

For named flexseqs, loop() yields bare values (matching peek_front()); use as.list() when you need names alongside values.

x <- flexseq(a = 1, b = 2, c = 3)
loop(for (el in x) print(el))

Plain for (el in x) (without loop()) does not dispatch to the iteration protocol — it walks the underlying finger-tree storage and yields internal nodes rather than sequence elements. Always wrap with loop().

Flexseqs convert back to standard R structures with as.list() (preserves names and list semantics) and unlist() (atomic vector when possible). length() returns the element count in O(1), and str() gives a compact diagnostic display.

x <- flexseq(a = 1, b = 2, c = 3)

as.list(x)
unlist(x)
length(x)


Try the Immutables package in your browser

Any scripts or data that you put into this service are public.

Immutables documentation built on April 29, 2026, 1:06 a.m.