March Docs

IOList

IOList module: lazy, tree-structured string builder.

IOList represents a sequence of string segments as a tree, deferring concatenation until the list is flushed to IO. This avoids O(n²) copying when building large strings from many small pieces.

Usage pattern: let buf = IOList.append(IOList.from_string("Hello, "), IOList.from_string("world!")) println(IOList.to_string(buf)) -- "Hello, world!"

The key invariant: IOList is lazy. Use it as an accumulator and only call to_string or write to IO at the very end.

Types

typeIOListIOList = Empty | Str(String) | Segments(List(IOList))#

Functions

fnappendappend(a : IOList, b : IOList) : IOList#

Append two IOLists, returning a new IOList that represents their concatenation. Neither argument is modified.

    IOList.to_string(IOList.append(IOList.from_string("foo"), IOList.from_string("bar")))
      -- "foobar"

Internally, Empty nodes are dropped.  No bytes are copied until `to_string`.

Cost: O(1) — no flattening until `to_string`.
fnbyte_sizebyte_size(iol : IOList) : Int#

Compute the total byte size of all segments in the IOList without flattening to a string.

    IOList.byte_size(IOList.from_string("hello"))  -- 5
    IOList.byte_size(IOList.empty())               -- 0

Cost: O(number of nodes in the tree).
fnemptyempty() : IOList#

The empty IOList — contains no bytes.

    IOList.to_string(IOList.empty())  -- ""

Cost: O(1).
fnfrom_stringfrom_string(s : String) : IOList#

Wrap a String as an IOList leaf node.

    IOList.to_string(IOList.from_string("hello"))  -- "hello"

No copy is made; the IOList holds a reference to the original string.
Cost: O(1).
fnfrom_stringsfrom_strings(xs : List(String)) : IOList#

Build an IOList from a List(String) by wrapping all elements as leaves.

    IOList.to_string(IOList.from_strings(Cons("a", Cons("b", Cons("c", Nil)))))
      -- "abc"

Cost: O(n) where n is the length of the list.
fnhashhash(iol : IOList) : String#

Compute a FNV-1a 64-bit content hash over the IOList segments without first flattening to a single string. Returns a 16-character lowercase hex string suitable for use as an ETag value.

    IOList.hash(IOList.from_string("hello"))   -- some hex string
    IOList.hash(IOList.empty())                -- "cbf29ce484222325"

The hash walks the IOList tree in order, hashing each `Str` segment's
bytes as encountered.  The result is identical to hashing the fully
flattened string.

Suitable for ETag generation and fragment cache keying.
Cost: O(total bytes across all segments).
fnis_emptyis_empty(iol : IOList) : Bool#

Return true if the IOList contains no bytes.

    IOList.is_empty(IOList.empty())            -- true
    IOList.is_empty(IOList.from_string(""))    -- true
    IOList.is_empty(IOList.from_string("x"))   -- false

Cost: O(1) for `Empty` and `Str`; O(n) for `Segments`.
fnprependprepend(s : String, iol : IOList) : IOList#

Prepend a String to an IOList.

    IOList.to_string(IOList.prepend("Hello, ", IOList.from_string("world!")))
      -- "Hello, world!"

Cost: O(1).
fnpushpush(iol : IOList, s : String) : IOList#

Append a String to an IOList.

    let buf = IOList.push(IOList.from_string("Hello"), ", world!")
    IOList.to_string(buf)  -- "Hello, world!"

Cost: O(1).
fnto_stringto_string(iol : IOList) : String#

Flatten an IOList into a single String by collecting all leaf segments and concatenating them.

    IOList.to_string(IOList.empty())                    -- ""
    IOList.to_string(IOList.from_string("hello"))       -- "hello"

This is the only function that allocates a full concatenated string.
Call it once at the end, not on every append.

Cost: O(total bytes across all segments).