CJC Demo 3: Neural Network Forward Pass

2-layer network with structs, pipe operator, and tensor matmul
CJC Language Neural Network All Assertions Passed

What This Proves

  • Structs — Layer { weights, bias } data type with field access
  • Pipe operator |> — matmul(x, w) |> add_bias(b) chains computations
  • Functions — relu_scalar, relu_1x4, add_bias, forward
  • Tensor runtime — matmul, element-wise ops, shape queries, to_vec
  • Multiple dispatch — same forward() works with any Layer struct
  • Consistency — manual computation matches struct-based computation exactly

Network Architecture

Input
[1, 3] x = [1, 2, 3]
Output
[1, 2] h @ W2 + b2

CJC Pipe Operator in Action

let hidden_pre = matmul(x, w1) |> add_bias(b1);
The |> pipe operator takes the result of matmul(x, w1) and feeds it as the first argument to add_bias. This desugars to: add_bias(matmul(x, w1), b1) — but reads left-to-right like a data pipeline.
let output = matmul(hidden, w2) |> add_bias(b2);
Same pattern for the output layer. CJC pipelines compose naturally.
CJC Source (struct-based forward pass)
struct Layer {
    weights: Tensor,
    bias: Tensor
}

fn forward(layer: Layer,
           input: Tensor) -> Tensor {
    matmul(input, layer.weights)
        |> add_bias(layer.bias)
}

// Build layers from weight tensors
let layer1 = Layer {
    weights: w1, bias: b1
};
let layer2 = Layer {
    weights: w2, bias: b2
};

// Forward pass
let h = forward(layer1, x);
let h_relu = relu_1x4(h);
let out = forward(layer2, h_relu);

// Verify both approaches match
assert_eq(output.get([0,0]),
          out.get([0,0]));
assert_eq(output.get([0,1]),
          out.get([0,1]));
Live Output — $ cjc run demo3_pipeline.cjc
=== 2-Layer Neural Network Forward Pass ===

Input x:
  Tensor(shape=[1, 3], data=[1, 2, 3])

Hidden pre-activation:
  Tensor(shape=[1, 4],
    data=[0.1, 0.0, 0.75, 1.0])

Hidden post-ReLU:
  Tensor(shape=[1, 4],
    data=[0.1, 0.0, 0.75, 1.0])

Network output:
  Tensor(shape=[1, 2],
    data=[0.17, 0.045])

Output shape: [1, 2]
All shape assertions passed!

Target:
  Tensor(shape=[1, 2], data=[1, 0])
Output - Target:
  Tensor(shape=[1, 2], data=[-0.83, 0.045])

Struct-based forward pass output:
  Tensor(shape=[1, 2],
    data=[0.17, 0.045])

Struct-based result matches manual!
=== Demo 3 complete ===