# 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`).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tinychain.net/advanced/client-side-class-inheritance.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
