Lens: The Engine of Transformation

Discover Lenses, τ's powerful mechanism for creating dynamic, zero-copy views and transformations on your temporal data.

A Lens is one of the most powerful and unique features of τ (tau). It represents a named, reusable transformation or calculation over one or more Schedules.

At its core, a Lens is a virtual, zero-copy view. It doesn't store data itself; instead, it defines an expression that is computed on-the-fly from its input schedules. This makes Lenses incredibly efficient for data exploration, real-time analysis, and schema evolution without costly data duplication or migration.

You can think of a Lens as:

  • A database view for temporal data.
  • A live data migration that transforms data at read-time.
  • A functional transformation applied across time series.

The Structure of a Lens

A Lens is defined by several key fields:

  • id: A unique []const u8 identifier (ULID).
  • name: A human-readable name for the Lens.
  • description: A brief explanation of what the Lens does.
  • expression: The mathematical or logical formula to be executed.
  • input_names: A list of the names of the schedules or other Lenses this Lens depends on.
libtau/primitives/lens.zig
1/// A lens represents a calculation or transformation identified by a unique id.
2pub const Lens = struct {
3  id: []const u8,
4  name: []const u8,
5  description: []const u8,
6  expression: []const u8,
7  input_names: std.ArrayList([]const u8),
8
9  // ... methods
10};

How Lenses Work

When you apply a Lens, it generates a new, temporary Schedule. The τ engine evaluates the Lens's expression for each time point, using data from the specified input_schedules. The result of each evaluation becomes a new Tau in the output schedule.

1. Creating a Lens

Creating a Lens involves defining its metadata and the core expression.

Example: Creating a Lens
1const allocator = std.heap.page_allocator;
2
3// Create a lens to calculate the average of two sensors.
4var avg_lens = try tau.primitives.Lens.create(
5  allocator,
6  "average_temp",
7  "Calculates the average of sensor_a and sensor_b.",
8  "(sensor_a + sensor_b) / 2",
9);
10defer avg_lens.deinit(allocator);
11
12// Specify the inputs required by the expression.
13try avg_lens.addInput(allocator, "sensor_a");
14try avg_lens.addInput(allocator, "sensor_b");

2. Applying a Lens

The apply method executes the Lens against the data source (represented by the engine). The engine is responsible for fetching the required input schedules. The apply method then returns a new, computed Schedule.

Example: Applying a Lens
1// 'engine' is an instance of the τ storage engine.
2// It knows how to fetch the 'sensor_a' and 'sensor_b' schedules.
3const avg_schedule = try avg_lens.apply(allocator, engine);
4defer avg_schedule.deinit(allocator);
5
6// avg_schedule now contains Taus where each data point is the
7// average of the corresponding points in 'sensor_a' and 'sensor_b'.

Advanced Capabilities

Temporal Offsets

Lenses support a powerful syntax for temporal offsets, allowing you to perform calculations across different points in time within your expression.

  • Index-based offset: variable[-n] refers to the value of variable n steps in the past.
  • Time-based offset: variable@-5s refers to the value of variable 5 seconds in the past from the current Tau's timestamp.

This enables complex temporal analyses like calculating moving averages, deltas, or rates of change directly within a Lens.

Example: Lens with a Temporal Offset
1// Lens to calculate the change from the previous value.
2var delta_lens = try tau.primitives.Lens.create(
3  allocator,
4  "temp_delta",
5  "Calculates the change in temperature from the previous reading.",
6  "temp - temp[-1]", // temp at current time minus temp at previous index
7);
8defer delta_lens.deinit(allocator);
9
10try delta_lens.addInput(allocator, "temp");
11try delta_lens.addInput(allocator, "temp[-1]");

Chaining Lenses

Lenses can depend on other Lenses. This allows you to build up complex, layered calculations from simpler, reusable components. The τ engine automatically resolves these dependencies and detects cyclical references to prevent infinite loops.

Cycle Detection: The apply method tracks visited Lenses to ensure that a Lens doesn't end up referencing itself in a circular dependency (e.g., A -> B -> A), which would cause an error.

Accessing Tau Metadata

Lenses can access the metadata of a Tau within an expression:

  • vt: Accesses the valid_ns (valid time in nanoseconds).
  • et: Accesses the expiry_ns (expiry time in nanoseconds).

This is useful for creating time-aware calculations. For example, you could create a Lens that only includes data from a certain time range.

Example: Time-aware Lens
1// Expression to filter out values older than a specific timestamp
2const time_filter_expr = "if(vt > 1672531200000000000, value, null)";

Last updated: 2/1/2026