# Anatomy of an Op

## Type annotations

```python
import tinychain as tc

# This is a native Python function.
# It doesn't need type annotations, although you can add them if you want,
# for example if they make the code more clear,
# or if you're using a linter which requires them.
#
# Since this is a native Python function, TinyChain code can only call it
# at compile-time.
def example1(b):
    a = 2
    return b + a, b * a

# This is a TinyChain Op. It can be evaluated at run-time.
# Every TinyChain Op has its own Context, which you can access
# by using the reserved names "cxt" or "txn" as the first argument of the Op.
#
# Since this is meant to be called at run-time, TinyChain doesn't know
# at compile-time what type of arguments to expect. So an Op needs
# type annotations in order to provide the correct API when compiled to JSON.
#
# For example, if you leave out the tc.Number annotation, TinyChain will
# provide a generic tc.State when this Op is compiled, and you'll get an
# error that says that tc.State doesn't support the "+" or "*" operators.
@tc.get_op
def example2(cxt, b: tc.Number) -> tc.Tuple:
    cxt.a = 2
    return b + cxt.a, b * cxt.a
```

Notice the type annotation `Number` on the parameter `b`. The actual value of parameter `b` may not be known when `example` is encoded as part of a graph configuration, so the type annotation is necessary in order to provide the correct API methods from inside the Python function definition. For example, if someone calls `example(URI("http://example.com/numeric_constant"))`, this is perfectly valid code, but it doesn't explicitly provide the type of the `Value` at `http://example.com/numeric_constant`.

Likewise, the return type annotation `Tuple` is provided for the benefit of the calling code. Without it, TinyChain would not know what type of `State` to return at encoding time (when the compute graph configuration is generated) and helper methods like `unpack` would be unavailable.

## The Op Context

The `cxt` parameter in the `example` function above is a `Context` object. It allows you to explicitly name the states which define your `Op`—in this case, there is one state called `b` passed in as an argument and one state called `a` defined in the body of the `Op`. TensorFlow users should find this familiar because it provides the same functionality as the `name_scope` function and `name=` keyword argument in TensorFlow.

{% hint style="danger" %}
Developers new to TinyChain sometimes find it confusing that the name "cxt" is used to refer to many different, independent contexts. Consider this (non-working!) example:

```python
import tinychain as tc

@tc.get_op
def my_constant() -> tc.Int:
    return 2

@tc.get_op
def example(cxt):
    return cxt.my_constant() * 5  # this is where the error happens!

if __name__ == "__main__":
    cxt = tc.Context()
    cxt.my_constant = my_constant
    cxt.example = example

    # note: here's a run-time call to the example op
    cxt.product = cxt.example()
```

This won't work! Calling `example` in this case will raise a `NotFoundException` because `example` has a *completely different and independent* `Op` context. You can verify this yourself by calling`tc.print_json(cxt)`from within the `example` function.

This should make intuitive sense. If `example` and `my_context` were hosted on different servers, would it make sense for the one to be able to access the other's internal state? What about from a security perspective?
{% endhint %}

## Automatic concurrency

Let's take another look at the `example` function:

```
@tc.get_op
def example(cxt, b: tc.Number) -> tc.Int:
    cxt.a = 2
    return cxt.a + b, cxt.a * b
```

The Python interpreter is imperative and single-threaded, so as a Python developer you're probably accustomed to the assumption that each line of your code will execute in exactly the order that it's written. TinyChain, however, is multi-threaded and features automatic concurrency (like TensorFlow). So, in the example above, `cxt.a + b` and `cxt.a * b` both execute simultaneously when a user executes your compute graph.

Automatic concurrency is crucial for a performant distributed runtime, but it comes with some trade-offs. For example, `Value`s are immutable:

{% hint style="danger" %}

```
@tc.get_op
def example(cxt, b: tc.Number) -> tc.Int:
    cxt.a = 1
    cxt.a += b  # this is where the error happens!
    return cxt.a
```

This won't work! If it did, it would be impossible for TinyChain to calculate the dependencies of each state in the `Op` context, and thus impossible to resolve independent states concurrently.
{% endhint %}

Only `Collections` like a `BTree`, `Table`, or `Tensor` are mutable. This introduces the need to handle *side-effects* with the `After` flow control, which we'll cover in the next section.
