Libmir Archive

ndslice field — Custom Iterators

mir.ndslice.field lets you build ndslice-compatible iterators over arbitrary memory layouts, computed values, or external data structures without copying.

Old URL: Previously at mir-algorithm.libmir.org/mir_ndslice_field.html. Redirects here.

A field in ndslice terminology is any type that satisfies the Field API: an opIndex(ptrdiff_t i) that returns an element. Fields are the lowest-level abstraction — Slice wraps a field (iterator) with shape and stride information.

Most users never write custom fields directly; they compose topology operations instead. Fields become useful when you need to:

  • Wrap an external data buffer with a non-standard memory layout
  • Expose a computed sequence as an ndslice
  • Integrate with C libraries that provide pointer + stride

The Field interface

A field must implement:

struct MyField {
    // Required: random access by linear index
    auto ref opIndex(ptrdiff_t index);

    // Optional but recommended: length
    @property size_t length() const;

    // Optional: slicing for sub-range views
    auto opSlice(size_t i, size_t j);
}

Built-in fields in mir.ndslice.field

IotaField — integer sequence

The field backing iota. Produces start + i on demand.

import mir.ndslice.field : IotaField;
import mir.ndslice.iterator : FieldIterator;
import mir.ndslice : slicedField;

auto field = IotaField!size_t(10);  // starts at 10
auto s = field.slicedField(5);      // [10, 11, 12, 13, 14]

RepeatField — broadcast a value

import mir.ndslice.field : RepeatField;
auto rf = RepeatField!double(3.14);
// rf[any_index] == 3.14

MapField — lazy element transform

The field backing topology.map. Wraps a field + a function.

import mir.ndslice.field : MapField;
// Usually created via map(), not directly

Writing a custom field

Example: a field that lazily reads rows from a memory-mapped file:

struct MMapField(T) {
    ubyte* ptr;
    size_t byteStride;  // bytes between elements

    T opIndex(ptrdiff_t i) const @nogc nothrow {
        // Cast through byte pointer so arithmetic uses bytes, not sizeof(T)
        return *(cast(T*)(ptr + i * byteStride));
    }
}

// Wrap as ndslice — stride of 8 bytes between float elements
auto s = MMapField!float(cast(ubyte*) filePtr, 8)
    .slicedField(1024);  // 1024-element slice

slicedField — turning a field into a Slice

import mir.ndslice : slicedField;

auto myField = SomeField(...);
auto s1d = myField.slicedField(100);         // 1-D
auto s2d = myField.slicedField(10, 10);      // 2-D

Iterators vs Fields

ConceptRole
FieldZero-dimensional: field[i] → element
IteratorOne-dimensional: advance by 1 with ++, dereference with *
SliceN-dimensional: wraps an iterator with shape + strides

Topology operations like map and zip typically work at the iterator level. For custom layouts, fields are often simpler to implement.

On this page