# Client-side Class Inheritance

## Client-side methods

The recommended way to do object-oriented programming in TinyChain is to host `Model` classes as part of a `Service`. This minimizes the amount of work the developer has to do in terms of keeping track of the difference between the compile-time and run-time state of their program. However, in some cases, instance methods must be defined entirely client-side, meaning they are defined and executed only at compile-time in order to construct `Op`s that are executed at run-time.

{% hint style="warning" %}
In general, you should use hosted class definitions (`Model`s). The only situations where you should use this more advanced but much more challenging technique are when:

1. A trivial helper class is needed for convenience, such as assigning specific names and types to a `Map` or `Tuple`
2. Program efficiency depends strongly on compile-time parameters, or
3. The application cannot be distributed as a hosted service
   {% endhint %}

For these reasons, client-side instance methods are used extensively in the TinyChain Python client. For example, [`Table.insert`](https://github.com/haydnv/tinychain/blob/main/client/tinychain/collection/table.py) is only defined in the client (the host has no `/insert` handler) because `insert` is not idempotent and therefore is not safe when replaying the low-level write operations recorded in a `Block` of a `BlockChain`. Code like this is not straightforward to read or write.

## Easy: helper classes based on Map and Tuple

Consider this example:

```python
@tc.post_op
def force(mass: tc.Tuple, acceleration: tc.Tuple) -> tc.Tuple:
    mass_quantity = tc.Number(mass[0])
    mass_unit = Mass(mass[1])
    acceleration_quantity = tc.Number(acceleration[0])
    acceleration_unit = Velocity(acceleration[1])**2
    return mass_quantity * acceleration_quantity, mass_unit * acceleration_unit
```

Because `Tuple` by itself doesn't contain any type information, there's a lot of boilerplate destructuring code here and a lot of room for error. For example, is the `Unit` of `acceleration` already `Velocity**2`? It's hard to see if there's a bug. Readability and usability can be improved by a helper class:

```python
class UnitQuantity(tc.Tuple):
    # note: there's no __uri__ defined here
    # which means this class definition only exists in the Python client,
    # i.e. it's not exported by any hosted service

    @property
    def quantity(self):
        return tc.Number(self[0])

    @property
    def unit(self):
        return Unit(self[1])

    def __mul__(self):
        # use self.__class__ as the return type here to better support subclasses
        return self.__class__((
            self.quantity * other.quantity,
            self.unit * other.unit))
```

Now the business logic is much more readable:

```python
@tc.post_op
def force(mass: UnitQuantity, acceleration: UnitQuantity) -> UnitQuantity:
    return mass * acceleration
```

## Challenging: construct a deep neural net

```python
class NeuralNet(tc.Tuple):
    """abstract methods omitted here"""

class DNN(NeuralNet):
    @classmethod
    def load(cls, layers):
        # the network architecture, like this parameter `n`,
        # must be known at compile-time in order to construct
        # an Optimizer for this ML model
        n = len(layers)

        # because the form of `DNN.forward` depends on `n`,
        # the `DNN.forward` method must be defined at compile-time
        class DNN(cls):
            def forward(self, inputs):

                # `layers` is defined in the compile-time context
                # so even though we have access to `layers` here,
                # we still have to reference `self[i]` instead of `layers`

                # otherwise the ops could fail at run-time because
                # they might depend on a state that was only defined
                # at compile-time
                
                state = self[0].forward(inputs)
                for i in range(1, n):
                    state = self[i].forward(state)

                return state

        # here the form of the returned instance is set to `layers`
        return DNN(layers)
```

This example highlights the distinction between compile-time and run-time state which the developer must be mindful of in order to define a client-side instance method. As a general rule, a parameter which is a native Python state known at compile-time (like the integer `n` above) is safe to reference when constructing a class or method, but a TinyChain `State` in the calling context has to be referenced using `self`. The nested class idiom (where the `create` method defines a custom subclass of `cls`) is only necessary when the structure of a compute graph which the class defines depends on a compile-time parameter (like `n` in `DNN.forward`).
