The Vale Programming Language

Generics are a different way to reuse code for different types.

We can use interfaces for this, but interfaces have some limitations.

Limitations of Interfaces

We often want to reuse a function for multiple types. On the interfaces page, we used an interface to allow the function to operate on any type that conformed to a certain contract.

A function that takes an interface doesn't have to care about the original type of the substruct we pass in.

However, that can also be its downside: it forgets the original type of the substruct we pass in. This can be mighty inconvenient, like in this example.

Notice how main is giving two Firefly to longerShip, so we know that longerShip should return a Firefly.

main doesn't know that though; all is sees is that longerShip returns an &ISpaceship.

Because of this, we can't call crazyIvan on the longer ship!

vale
interface ISpaceship {
func length(virtual this &ISpaceship) int; 0
func honk(virtual this &ISpaceship) void;

}

func longerShip(
a &ISpaceship,
b &ISpaceship)

&ISpaceship {
if (a.length() > b.length()) {
return a;
} else {
return b;
}
}


struct Raza { ... }
func length(r &Raza) int { 4 }
func honk(r &Raza) { ... }
impl ISpaceship for Raza;

struct Firefly { ... }
func length(f &Firefly) int { 3 }
func honk(f &Firefly) { ... }
impl ISpaceship for Firefly;

func crazyIvan(f &Firefly) { ... }

exported func main() {
flametail = Firefly(...);
serenity = Firefly(...);
ship = longerShip(
&flametail, &serenity)
;


// ship.crazyIvan();
// Compile error: ship is type
// ISpaceship, not Firefly!
}

Side Notes
(interesting tangential thoughts)
0

In future versions, this can be shortened to just func length(this) int;.

Using Generics

We can make longerShip into a generic function.

Its parameters aren't exactly ISpaceship, they are any type T.

Since the parameters and return type are all T, the compiler knows that if we hand in Fireflys, we return a Firefly.

In this case, we're calling it like longerShip<Firefly>(...), which specifies that we should use the version of longerShip where T is a Firefly.

longerShip<T> returns a T, so main knows that longerShip<Firefly> returns a Firefly.

vale
interface ISpaceship {
func length(virtual self &ISpaceship) int;
func honk(virtual self &ISpaceship);

}

func longerShip<T> 1
(a &T, b &T) &T {
if (a.length() > b.length()) {
return a;
} else {
return b;
}
}


struct Raza { ... }
impl ISpaceship for Raza;
func length(r &Raza) int { 4 }
func honk(r &Raza) { ... }

struct Firefly { ... }
func length(f &Firefly) int { 3 }
func honk(f &Firefly) { ... }
func crazyIvan(f &Firefly) { ... }
impl ISpaceship for Firefly;

exported func main() {
flametail = Firefly(...);
serenity = Firefly(...);
ship =
longerShip<Firefly>( 2
&flametail, &serenity)
;
// This works now!
ship.crazyIvan();

}

1

In future versions, this can be <T impl ISpaceship> which will give a better compile error if someone calls longerShip with a non-ISpaceship.

2

We can actually leave off the <Firefly> here, the compiler can figure it out from the arguments we passed in.

Generic Structs

We can make generic structs too.

Here we're making a Flock struct that can hold multiple of the same ship.

vale
// Using above imports and
// ISpaceship, Firefly, Raza

struct Flock<T> {
ships List<T>;
}

exported func main() {
f = Flock<Firefly>(List<Firefly>());
f.ships.add(Firefly(...));
f.ships.add(Firefly(...));

firstShip = f.ships.get(0);
// This works because the compiler
// can see that firstShip is a
// Firefly.
firstShip.crazyIvan();

}

Next: Patterns