I’ve always been fascinated by how Elixir’s Stream module handles lazy
operations by storing the operation data in a struct. 🤯

It’s so simple but so brilliant!

You can see that if you dig into the source code:

``````defmodule Stream do
# ...
defstruct enum: nil, funs: [], accs: [], done: nil
# ...
end
``````

It defines a struct with `funs` (among other things) to store the functions to
apply lazily.

And I know `Stream` is not the only module to do that. Ecto.Multi also stores
operations
in a struct, and I’m sure there are others.

## Lazy Math

So, I wanted to see what it’d be like to write a simple implementation of a lazy
math evaluator.

Unsurprisingly, Elixir makes it easy to do this.

Let’s take a look:

``````defmodule LazyMath do
defstruct initial: 0, ops: []

def new(initial), do: %LazyMath{initial: initial}
end
``````

We first define a `LazyMath` module with struct definition that has the
`initial` value set to `0` and an empty list of `ops` (operations).

We also add a `new/1` function that will be a helper to initialize our struct.

Now let’s add some operations: `add/2`, `subtract/2`, `multiply/2` and
`divide/2`:

``````  def add(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:add, number} | ops]}
end

def subtract(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:sub, number} | ops]}
end

def multiply(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:mult, number} | ops]}
end

def divide(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:div, number} | ops]}
end
``````

As you can see, they all look very similar (and we could probably refactor a
common private function). But what’s interesting is that we’re storing a
representation of the operation instead of performing the operation.

So, when we want to add a number, we prepend an `{:add, number}` tuple to the
list of existing `ops`, and return the updated `%LazyMath{}` struct.

``````  def add(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:add, number} | ops]}
end
``````

The rest of the operations work the exact same way (though we store different
tuples).

Now, let’s see how we can evaluate all of the operations:

``````  def evaluate(%LazyMath{initial: init, ops: ops}) do
ops
|> Enum.reverse()
|> Enum.reduce(init, fn
{:add, number}, acc_total -> acc_total + number
{:sub, number}, acc_total -> acc_total - number
{:mult, number}, acc_total -> acc_total * number
{:div, number}, acc_total -> div(acc_total, number)
end)
end
``````

Our `evaluate/1` function takes an existing `%LazyMath{}` struct, pattern
matching the initial value and the operations.

We then reverse the list of operations, so we can apply them in the correct
order — remember we were prepending new operations before.

Finally, we `Enum.reduce/3` over the list of operations (now in order),
passing the initial value, and then we pattern match on the operation tuple to
perform the actual operation on the accumulated total.

Here’s the full module:

``````defmodule LazyMath do
defstruct initial: 0, ops: []

def new(initial), do: %LazyMath{initial: initial}

def add(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:add, number} | ops]}
end

def subtract(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:sub, number} | ops]}
end

def multiply(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:mult, number} | ops]}
end

def divide(math = %{ops: ops}, number) do
%LazyMath{math | ops: [{:div, number} | ops]}
end

def evaluate(%LazyMath{initial: init, ops: ops}) do
ops
|> Enum.reverse()
|> Enum.reduce(init, fn
{:add, number}, acc_total -> acc_total + number
{:sub, number}, acc_total -> acc_total - number
{:mult, number}, acc_total -> acc_total * number
{:div, number}, acc_total -> div(acc_total, number)
end)
end
end
``````

Let’s test how lazy we are:

``````result =
LazyMath.new(0)
|> LazyMath.subtract(2)
|> LazyMath.multiply(2)
|> LazyMath.divide(3)
# => %LazyMath{initial: 0, ops: [div: 3, mult: 2, sub: 2, add: 5]}
``````

As you can see, our `result` hasn’t evaluated any math operations yet. Instead,
it stored the initial value along with the list of operations. 🥳

Finally, we can evaluate the `result`:

``````result |> LazyMath.evaluate()
# => 2
``````

Pretty cool, right?