One of the most important aspects of the Nessa type system are **templates**. These allow you
to pass types as parameters in order to create other types. This is mainly used in function and class
definitions to prevent code repetition. In this section, we will only explain the basic syntax and
semantics of templates.

## Introduction

A **template** can be defined as a placeholder for another type to go into. They allow the programmer to write typed structures
such as "lists of *T*", where *T* is a type with certain properties without the need of rewriting the class for each possible *T*.
The implementation can be a litle more involved, but it pays off as a library writer.

## Syntax

Type templates are specified in the context of **structural types**, **classes**, **functions** and **operations**. Each of these
has their own syntax and diving into the specifics would be outside the scope of the type system, but we will talk about the syntax
when templates are already defined.

Templates are always represented with an single quote character before an alphanumerical identifier that follows the same rules as
class names. Some examples of valid template names would be `'Test` or `'Parameter_1`. Names such as `'0123` or names with non-ASCII
characters are not allowed.

Now, this is the way to reference a template that has already been defined, but when you have class that **depends** on one or more templates (i.e.
a **parametric class**), you also have to specify the type parameters when referring to the class. For example, you cannot refer to the `Array` class
without specifying the type of data that goes inside the array as the first (and only in this case) type parameter. These parameters are specified 
the same way as in languages such as C++, with angle brackets. These would be some examples:

```
Array<Int>              // Array of Ints
Array<Float | String>   // Array of either Floats or Strings
Array<*>                // Array of anything
Array<'T>               // Array of 'T (only if 'T is in scope)
```

Of course, we can define a class with an arbitrary amount of type parameters, but Nessa does not include any with more than one by default. If we had an
hypotetical class called `HashMap` that had two parameters (the type of the key and the type of the value), we could refer to it like this:

```
HashMap<Int, Int>                 // HashMap from Ints to Ints
HashMap<String, Float | String>   // HashMap from Strings to either Floats or Strings
HashMap<*, *>                     // HashMap from anything to anything
HashMap<Bool, 'T>                 // HashMap from Bool to 'T (only if 'T is in scope)
```

## Semantics

Like we said before, a template is not exactly a type, but a placeholder for a type that will be substituted by a proper type later. This is done to
prevent manual code repetition and allow the creation of more general code. 

How this works is pretty simple. Every time you create a **parametric class** and refer to it anywhere in the code, the interpreter will substitute
the parameters automatically for the ones given inside the angle brackets. This can be essentially understood as creating a new class for each distinct
instance of a parametric class. This mechanism works exactly the same way for **parametric functions**, **structural types** and **operations**.

Of course, the fact that you substitute a type does not mean that the code magically works. In fact, it might fail to compile if you try to call a function 
overload that does not exist. The main way to avoid this is **bounded substitution** via **interfaces**.

## Bounded substitution

> **Note:** this works as of now, but syntax will probably change in future releases

This mechanism allows the programmer to specify constraints while defining a parametric type instance. One example of why you would want to do this is that
`HashMap` class we discussed earlier. As you might know, a *HashMap* structure requires keys to be **hashable** because of how data is stored inside for fast 
retrieval, but not all types might be hashable and you would also want to user to know that they have to implement a hashing function for a custom type
before they can put it as a *HashMap* key.

This is what **interfaces** do, they allow you to define APIs and assert whether or not a type follows it or not. If you define an interface called `Hashable`
that checks whether or not the type that implements it (called `Self`) has a function called `hash` that takes a `Self` and returns an `Int`, you can use it as a
**constraint** for template substitution.

This woult be done simply by changing the references to `'T` inside the class definition to `'T [Hashable]`, which translates to *a T which is Hashable*. You can put as
many constraints as you want separated by commas and the constraints can also be parametric, following the same syntax rules as types.