## Classes and Objects

Like we have seen earlier with values and variables, it can be hard
to pin down what objects and classes really are. Different languages
give different semantics to what initially appears to be the same
concept. Let's try to unravel the mess, of course with the semantics
of Python firmly in our crosshairs.

In Python, «objects» and «cells» (from last week) are essentially
the same thing. Outside of Python, the main difference is that
objects bundle up «operations» (methods) in addition to data, but:

 1. functions are really just values, so the semantic difference
    isn't huge (they can naturally appear as sub-values of a value
    stored in a cell),
 2. there are no cells «without» methods in Python anyway – even
    traditional ‘simple data’ like integers come with methods.

While this may seem uncharacteristically simple, worry not: there is
enough magic associated with Python objects (and we will get to it),
but the magic is universal in the sense, that all (Python) cells are
really objects.

So what about the other ingredient, classes? There are multiple ways
to look at classes that are somewhat orthogonal (their applicability
to different programming languages varies):

 1. first and foremost, «classes are types» (mind you, not all types
    need to be classes, though in Python classes and types are
    «almost» the same thing) – that is, we can take a value (object)
    and decide whether it is an «instance»¹ of a particular class
    (i.e. it is a member of the type to which the class
    corresponds),
 2. what more, classes are «structured» types – they really are
    «products», in the sense that an object has multiple «fields»
    or «sub-values» that all exist at the same time (as opposed to
    «sum» types); in this sense, classes are like tuples,
 3. classes describe a «protocol»: the names of methods, their
    signatures, and so on, that all instances of the class conform
    to (this is «not quite» the case in Python, but we can ignore
    that for now),
 4. classes provide an «implementation» of this protocol:² method
    bodies appear in the definition of the class, and these bodies
    are shared by all instances:
    ◦ this saves space, because there is no need to store each
      method in each instance,
    ◦ it goes a long way toward «consistency» of behaviour: if a
      class only prescribes a protocol (which is only «syntax»), its
      instances are free to disagree on the behaviour – the
      «semantics» of a particular method,
 5. classes are «factories»: they provide means to «create new
    instances» (i.e. to create new objects),
 6. classes may be «objects» in their own right, with data and
    methods, like any other objects; they may even be «instances» of
    some class (in Python, they are instances of the class ‹type›…
    by default) – that class is then known as a «metaclass» and, you
    guessed it, we will get to that some other day.

Especially interesting is reviewing points 1-5 in the light of
point 6. What are the implications?

 1. since classes are types, and classes are also objects, that
    means that «types» are objects and that means that they «exist
    at runtime» – there is no type erasure in Python (well, we
    already knew this),
 2. perhaps more interestingly, this means that we can do
    «type-level reflection» at runtime ‘for free’, simply by calling
    methods on the class,
 3. since classes exist as objects, instances can keep a (runtime)
    reference (this is what happens in Python), and «delegate» to
    this object in case they do not have a method ‘of their own’
    (this is how implementation sharing happens); this is also part
    of the reason why methods have an explicit ‹self› argument,
 4. because function calls are an operator and operators are defined
    by objects, «calling a class» is a perfectly legitimate thing to
    do: this simply runs a specific method of the class – this is
    how objects are created (but it gets complicated and involves
    metaclasses; we are shelving this for now),
 5. «inheritance» is «also» implemented as delegation
    (unfortunately, because Python supports unrestricted multiple
    inheritance, the algorithm for lookup is ungodly).

¹ We will use the term instance to refer to objects «in relation» to
  ‘their’ class. Not all objects are necessarily instances in all
  programming languages, but in many languages, each object is an
  instance of some class.

² In some languages, there are special types of classes (abstract
  classes, interfaces) and/or methods (abstract methods, pure
  virtual methods) that do «not» provide an implementation. This is
  normally not needed in Python, because of «duck typing». If
  required, such methods can be implemented as ‹raise
  NotImplemented()› – we will deal with exceptions some other day.
