# Flow control: after, cond, while\_loop

## cond

When using Python to develop a TinyChain service, it’s important to remember that the output of your code is a compute graph which will be served by a TinyChain host; your Python code itself won’t be running in production. This means that you can’t use Python control flow operators like `if` or `while` the way that you’re used to. For example:

```python
@tc.get_op
def to_feet(txn, meters: tc.Number) -> tc.Number:
    # IMPORTANT! don't use Python's if statement! use tc.cond!
    return tc.cond(
        meters >= 0,
        meters * 3.28,
        tc.error.BadRequest("negative distance is not supported"))
```

## after

It’s also important to keep in mind that TinyChain by default resolves all dependencies concurrently, and does not resolve unused dependencies. Consider this function:

```python
@tc.post_op
def num_rows(txn):
    max_len = 100
    schema = tc.table.Schema(
        [tc.Column("user_id", tc.Number)],
        [tc.Column("name", tc.String, max_len), tc.Column("email", tc.String, max_len)])

    txn.table = tc.table.Table(schema)
    txn.table.insert((123,), ("Bob", "bob.roberts@example.com"))
    return txn.table.count()
```

This Op will *always* resolve to *zero*. This may seem counterintuitive at first, because you can obviously see the `table.insert` statement, but notice that the return value `table.count` does not actually depend on `table.insert`; `table.insert` is only intended to create a side-effect, so its result is unused. To handle situations like this, use the `after` flow control:

```python
@tc.post_op
def num_rows(txn):
    max_len = 100
    schema = tc.schema.Table(
        [tc.Column("user_id", tc.Number)],
        [tc.Column("name", tc.String, max_len), tc.Column("email", tc.String, max_len)])

    txn.table = tc.Table(schema)
    return tc.after(
        txn.table.insert((123,), ("Bob", "bob.roberts@example.com")),
        txn.table.count())
```

Now, since the program explicitly indicates that `table.count` depends on a side-effect of `table.insert`, TinyChain won’t execute `table.count` until after the call to `table.insert` has completed successfully.

## while\_loop

Loops are probably the most difficult part of TinyChain to get used to if you've never used a graph runtime before.

Consider this simple while loop:

```python
i = 0
while i < 10:
    i += 1
```

In a TinyChain compute graph, you have to account for the facts that a) you need the loop to run at execution time (when a user executes your graph), not encoding time (when the TinyChain Python client exports your graph configuration as JSON), and b) TinyChain `Value`s are immutable:

```python
import tinychain as tc

@tc.get_op
def loop(until: tc.Number) -> tc.Int:
    # the closure decorator captures referenced states from the outer scope,
    # in this case "until"
    @tc.closure
    @tc.post_op
    def cond(i: tc.Int):
        return i < until

    @tc.post_op
    def step(i: tc.Int) -> tc.Int:
        return tc.Map(i=i + 1)  # here we return the new state of the loop

    initial_state = tc.Map(i=0)  # here we set the initial state of the loop

    # return the loop itself
    return tc.while_loop(cond, step, initial_state)
```

## Nested conditionals

Consider this example:

{% hint style="danger" %}

```python
@tc.post_op
def maybe_delete(table: tc.table.Table, should_update: tc.Bool):
    a = tc.cond(should_update,
        table.update({"column": "value"}),
        table.delete())

    # this won't work!
    return tc.cond(table.is_empty(), tc.BadRequest("empty table"), a)
```

{% endhint %}

In this case, TinyChain is unable to resolve the dependencies of the state to `return` without executing *both* branches of `a`, which would make `a` no longer a conditional (the `table` would be deleted!). For this reason, nested conditionals are not allowed. The most foolproof way to handle a nested conditional is to use an `Op`:

{% hint style="success" %}

```python
@tc.post_op
def maybe_delete(table: tc.table.Table, should_update: tc.Bool):
    return tc.cond(should_update,
        table.update({"column": "value"}),
        table.delete())

@tc.post_op
def check_result(table: tc.table.Table, should_update: tc.Bool):
    return tc.cond(table.is_empty(),
        tc.error.BadRequest("empty table"),
        maybe_delete(table, should_update))
```

{% endhint %}

## Examples: updating a Tensor conditionally

For more detailed examples on how to use common flow controls, take a look at the [client tests](https://github.com/haydnv/tinychain/tree/main/tests/tctest/client).
