nessa-language 0.9.1

An extensible programming language with a strong type system
Documentation
Nessa does not allow inheritance-based polymorphism as in most programming languages. Instead, generics are
entirely based on **bounded template substitution**. In order to use this, we need to define **interfaces** and
**implement** them for out types. In this section we will define the syntax and semantics of this mechanism.

## About interfaces


An **interface** can be defined as a set of function and operation signatures that may or may not be fulfilled by a target type 
which is marked as `Self`. We say that a type `T` fulfills the interface if every function and operation definition inside the
interface exists when we substitute `Self` by `T`.

Also, when a type fulfills an interface, you can **implement** it, which means that we mark thye type so that
the interpreter knows it and we can make use of bounded substitution on that interface with that type. Implementations
can also be **parametric**.

## Syntax


We can define an interface using the following syntax:

```
// Simple
interface Example {
    fn example_1(v: Self);
    fn example_2(...) -> Type;

    [...]

    op prefix_op (v: TypeV) -> Type;

    [...]
    
    op (v: TypeV) postfix_op -> Type;

    [...]
    
    op (a: TypeA) binary_op (b: TypeB) -> Type;

    [...]
    
    op (a: TypeA) nary_open args nary_close -> Type;
}

// With generics
interface Example<T> {
    fn example_1(v: Self, n : 'T);
    fn example_2(...) -> Type;

    [...]

    op prefix_op (v: 'T) -> Type;

    [...]
    
    op (v: 'T) postfix_op -> Type;

    [...]
    
    op (a: TypeA) binary_op (b: 'T) -> Type;

    [...]
    
    op (a: 'T) nary_open args nary_close -> Type;
}
```

Then we can implement it using the following syntax:

```
// Simple
implement Example for Type;

// With generics
implement Example<OtherType> for Type;

// Generic implementation (define for all arrays)
implement<T> Example<OtherType> for Array<'T>;
```

If `Type` does not fulfill `Example`, the interpreter will raise an error.

## Semantics


The best way to understand how this works is with an example. Imagine that you want to create a function that
takes an argument of a type that can be converted to `Int`. You could start by writing this:

```
fn test(n: Int) -> Int {
    [...]
}
```

This is problematic because you would have to convert everything on call time. To avoid this you could write something like this:

```
// An overload for each supported type
fn convert(n: Int) -> Int {
    return n.deref();
}

fn convert(n: Float) -> Int {
    return n.floor();
}

// Use generics
fn<T> test(n: 'T) -> Int {
    let converted: Int = convert(n.deref());
    [...]
}
```

The problem with this approach is that you rely on the `convert` function being defined for `'T` and that might not be the case. Even worse,
it could be defined with wrong types and cause difficult to debug errors. In order to avoid all this, you can use an interface and **bounded
substitution**:

```
// Define interface
interface Convertible {
    fn convert(n: Self) -> Int;
}

// An overload for each supported type
fn convert(n: Int) -> Int {
    return n.deref();
}

fn convert(n: Float) -> Int {
    return n.floor();
}

// Explicit implementations
implement Convertible for Int;
implement Convertible for Float;

// Use bounded generics
fn<T> test(n: 'T [Convertible]) -> Int {
    let converted: Int = convert(n.deref());
    [...]
}
```