The Vale Programming Language

Normally, our code needs to know the exact type of a struct to be able to work with it.

In this example, hopscotch only works for a Human.

If we want a hopscotch for DarkElf too, we'd have to copy and paste the hopscotch function and adjust it to use DarkElf instead. How tedious!

It would be great if we could make a function accept any kind of reference, as long as it has the right functions (such as hop and skip).

We can, using interfaces!

Vale

struct Human { ... }
fn hop(s &Human) { ... }
fn skip(s &Human) { ... }

fn hopscotch(s &Human) {
s.hop();
s.skip();
s.hop();
}


struct DarkElf { ... }
fn hop(s &DarkElf) { ... }
fn skip(s &DarkElf) { ... }

fn main() export {
wulfgar = Human(...);
hopscotch(&wulfgar);
}

Using Interfaces

We can create an interface with the interface keyword, like Bipedal here.

Then, to tell the compiler that Human and DarkElf conform to that interface, we use the impl keyword. 0

Last, we change hopscotch to take in a &Bipedal, and now we can call it with a Human or DarkElf, since both implement the Bipedal interface.

Vale

interface Bipedal {
fn hop(virtual b &Bipedal) void; 1
fn skip(virtual b &Bipedal) void;
}


struct Human { ... }
fn hop(h &Human impl Bipedal) { ... }
fn skip(h &Human impl Bipedal) { ... }
impl Bipedal for Human;

fn hopscotch(b &Bipedal) {
b.hop();
b.skip();
b.hop();
}


struct DarkElf { ... }
fn hop(s &DarkElf impl Bipedal) { ... }
fn skip(s &DarkElf impl Bipedal) { ... }
impl Bipedal for DarkElf;

fn main() export {
wulfgar = Human(...);
hopscotch(&wulfgar);
drizzt = DarkElf(...);
hopscotch(&drizzt);
}

Notes
0

When a struct implements an interface, we call it a substruct of that interface.

1

Future versions will have a much simpler syntax, see the bottom of the page for a preview!

Open Interface Constructors

(Also known as "Anonymous Substructs")

When we make an open interface like the above Bipedal, the compiler makes an open interface constructor, a function which can define another substruct.

It takes functions (or lambdas) as arguments, to use for the interface's methods.

In this example, we're giving it a lambda for its hop method, and another lambda for its skip method.

Vale
interface IShip {
fn launch(virtual ship &IShip);
}


fn main() export {
x = IShip((){ println("Launching!"); });
// x is an unnamed substruct which
// implements IShip.

x.launch();
}

Planned Features

These are planned features for Vale. See the roadmap for plans!

Simplified Syntax

The current syntax is closer to the way Vale internally thinks about interfaces. However, an easier syntax is planned:

  • The methods could be inside the interface.
  • We could use self instead of virtual self &Bipedal
  • We could have the impl inside the struct.
  • We could have override methods inside the impl.

interface Bipedal { fn hop(self) void; fn skip(self) void; } struct Human { ... impl Bipedal { fn hop(&self impl) { ... } fn skip(&self impl) { ... } } }

Sealed Interfaces

By default, an interface is open, which means any struct can implement it.

A sealed interface can only be implemented by structs defined in the same file.

Vale
interface MaybeInt sealed { }
// sealed: only the below two
// structs can implement MaybeInt.

struct NoInt { }
impl None for MaybeInt;

struct SomeInt { value int; }
impl SomeInt for MaybeInt;

Because the compiler knows all the implementing structs up-front, it:

  • Can be inlined. 2
  • Checks that mat statements consider all substructs.
  • Generates sealed interface constructors.

2

Locals and members can inline sealed interfaces. The compiler will figure out the size of the largest substruct, and reserve that much memory in the stack (if an inl local) or the containing struct (if an inl member), and use that memory for the substruct. (This is the equivalent of a C/C++ union or Rust enum)

Sealed Interface Constructors

A sealed interface constructor is a function with the same name of the interface, which constructs the correct subclass depending on the given arguments.

In this example, when we call MaybeInt(7), the compiler finds that the SomeInt substruct indeed has a constructor accepting one int, and calls that.

Vale
// Using above MaybeInt/NoInt/SomeInt

fn main() export {
x = MaybeInt(7);
// x is of type SomeInt.

y = MaybeInt();
// y is of type NoInt.
}

A sealed interface constructor also allows us to make substructs via shortcalling. 3

3

See Structs for more on shortcalling.

Shortcalling Sealed Interface Constructors

We normally call a function by name, such as the MaybeInt(7) above. However, if the code is expecting a certain type, it can automatically call the constructor of the expected type.

Vale
// Using above MaybeInt/NoInt/SomeInt

fn main() export {
// These statements are equivalent:
x MaybeInt = MaybeInt(7);
x MaybeInt = (7);
}

This saves a lot of typing when calling functions.

Vale
// Using above MaybeInt/NoInt/SomeInt

fn foo(m MaybeInt) { ... }

fn main() export {
foo(SomeInt(7));
// Equivalent, calling sealed
// interface constructor:
foo(MaybeInt(7));
// Equivalent, shortcalling sealed
// interface constructor:
foo((7));

foo(NoInt());
// Equivalent, calling sealed
// interface constructor:
foo(MaybeInt());
// Equivalent, shortcalling sealed
// interface constructor:
foo(());
}

Next: Generics