Client-side Class Inheritance
Learn how to inherit from and modify the behavior of TinyChain client classes

The recommended way to do object-oriented programming in TinyChain is to host 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 Ops that are executed at run-time.
In general, you should use hosted class definitions. The only situations where you should use this more advanced but much more challenging technique are when:
  1. 1.
    A trivial helper class is needed for convenience, such as assigning specific names and types to a Map or Tuple
  2. 2.
    Program efficiency depends strongly on compile-time parameters, or
  3. 3.
    The application cannot be distributed as a hosted service
For these reasons, client-side instance methods are used extensively in the TinyChain Python client. For example, Table.insert is only defined in the client (the host has no /insert handler in the host) 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.

Consider this example:
@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:
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:
@tc.post_op
def force(mass: UnitQuantity, acceleration: UnitQuantity) -> UnitQuantity:
return mass * acceleration

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).
Copy link
On this page
Client-side methods
Easy: helper classes based on Map and Tuple
Challenging: construct a deep neural net