# Channels and series

> How Arc connects to Synnax telemetry through channels and handles array data with series

Channels are Arc’s connection to Synnax telemetry data. They let you read sensor values, write to actuators, and respond to operator inputs. Series are arrays of values, useful for batch processing and aggregation.

## Channels

A channel in Arc references a channel that exists in your Synnax cluster. You don’t declare channels. Just use them by name:

```arc
// Read from a pressure transducer and scale it into another channel
tank_pressure -> scale{factor=2.0} -> pressure_scaled
// Write a fixed value to a valve command
true -> valve_cmd
```

Channel names follow the same conventions as Synnax channels: start with a letter or underscore, then letters, digits, or underscores. Examples: `tank_pressure`, `inlet_temp`, `start_btn`.

### Channel discovery

The Arc text editor provides autocomplete for channels in your cluster. As you type, matching channel names appear with their data types. You can also browse available channels in the Console’s resources panel before writing code.

## Reading channels

Channels behave differently depending on whether you’re in a **reactive context** (flow statements) or an **imperative context** (inside a function body).

**Flow**

In flow statements, channels are **event-driven data sources**:

```arc
sensor -> controller{} -> actuator
```

When new data arrives on `sensor`, the entire pipeline executes.

**Func**

Inside function bodies, channel reads are **non-blocking snapshots**:

```arc
func controller{setpoint chan f64} (pressure f64) f64 {
    error := pressure - setpoint // Read latest value (non-blocking)
    return error
}
```

You can also bind a channel, or an expression over channels, to a [variable](https://docs.synnaxlabs.com/reference/control/arc/reference/variables) that updates as new data arrives.

| Aspect        | Reactive (`->`)     | Imperative (function body)    |
| ------------- | ------------------- | ----------------------------- |
| Trigger       | New data arrival    | Function invocation           |
| Data form     | Time-series stream  | Single scalar value           |
| Empty channel | Skips execution     | Returns zero                  |
| Use case      | Main data pipelines | Side-channel reads, setpoints |

### Channels as inputs

Channels can be passed to functions as input parameters:

```arc
func monitor{sensor chan f64, limit f64} () bool {
    return sensor > limit
}
// Wire it up on a 50ms cadence
time.interval{period=50ms} -> monitor{sensor=tank_pressure, limit=500.0} -> pressure_high
```

This lets you write reusable functions that work with different channels.

## Writing to channels

Write to a channel by making it the final target of a flow statement, or with assignment syntax inside a function.

**Flow**

```arc
stage control {
    sensor -> scale{factor=2.0} -> output_channel
}
```

**Func**

```arc
func control{valve chan bool} (pressure f64) {
    if pressure > 500 { valve = true }
}
```

Writes are queued until the end of the execution cycle. Multiple writes to the same channel in a single cycle result in the last value being sent.

## Series

Series are homogeneous arrays of values. They’re useful for batch operations and working with buffered data.

### Creating series

```arc
// Literal
data := [1.0, 2.0, 3.0]

// Empty (requires type annotation)
buffer series f64 := []
```

### Indexing and slicing

```arc
data := [10.0, 20.0, 30.0, 40.0]

first := data[0]  // 10.0
mid := data[1: 3] // [20.0, 30.0]
last := data[3]   // 40.0
```

Out-of-bounds access causes a runtime error.

### Series length

```arc
data := [1.0, 2.0, 3.0]
count := len(data) // 3 (i64)
```

## Element-wise operations

Arithmetic and comparison operators work element-wise on series:

```arc
data := [1.0, 2.0, 3.0]

// Scalar operations (broadcast)
scaled := data * 2.0  // [2.0, 4.0, 6.0]
offset := data + 10.0 // [11.0, 12.0, 13.0]

// Series operations (must be equal length)
other := [4.0, 5.0, 6.0]
sum := data + other // [5.0, 7.0, 9.0]

// Comparisons return series bool
mask := data > 2.0  // [false, false, true]
```

Binary operations between two series require them to have equal length. Mismatched lengths cause a runtime error.

## Channel types

When passing channels as input parameters, you declare their type:

```arc
func process{input chan f64, output chan f64} () {
// ...
}
```

The type system ensures you don’t accidentally connect incompatible channels. If you try to read an `f64` channel and use it where an `i32` is expected, the compiler will catch it.
