Boson

Algebraic Data Types

Why have them?

Algebraic data types let us solve a couple issues.

They let us avoid the need for nil pointers

They let us return errors from functions, as opposed to:

Rationale

Consider reading from a file.

In C

C has the read function that reads from file descriptors.

// If successful, the number of bytes actually read is returned.
// Upon reading end-of-file, zero is returned.
// Otherwise, a -1 is returned and the global variable errno is set to indicate the error.
ssize_t read(int fildes, void *buf, size_t nbyte);

Assuming fd is a valid file descriptor

char buf[1024];
n := read(fd, buf, 1024);
if (n = -1) {
	int err = errno;
	// handle error
	return
}
doStuffWith(buf, n);

In Go

Go has the Reader interface. Signature of a Reader is:

type Reader interface {
    Read(p []byte) (n int, err error)
}

Assuming r is an open Reader

buf := make([]byte, 1024)
n, err := r.Read(buf)
if err != nil {
	// handle error
	return
}
doStuffWith(buf[:n])

In Boson

In Boson we want something like Go’s interfaces. However we want to be able to return a single algebraic Result type that can be a success value or an error.

type Reader interface {
    Read(p []byte) Result[int]
}

Assuming r is an open Reader

var buf byte[1024] <!-- TODO: address array syntax -->
m := r.Read(buf)
match m {
case Error(e):
	// handle error
case Ok(n):
	doStuffWith(buf, n)
}

Syntax

Declaration

The declaration syntax will be as follows:

type Option[T] {
	Some(T)
	None
}

This declares the Option[T] type, with two type constructors, Some and None. Option[T] has a type parameter T, and that is used to parameterize the constructor Some.

The syntax should allow for multiple type parameters:

type Pair[L, R] {
	Pair(L, R)
}

Usage

Using a type once it’s been declared should be straight forward. The type constructors in the declaration are callable, and construct a value of the type.

var o Option[int] = Some(10)
var p Pair[int, bool] = Pair(10, true)

Incomplete and Concrete types

Incomplete types

Option[T] is an example of an incomplete type. It is incomplete because T does not name a concrete type, and is used as a parameter. The only valid place to use an incomplete type is in a type definition. Everywhere else, we need a concrete type.

Concrete types

Option[int] is an example of a concrete type. It is concrete because int names another concrete type. We can use Option[int] anywhere we would use any other concrete type. Incomplete types give us prototypes for creating concrete types. We can use any concrete type in place of the type parameter in an Incomplete type. That includes primitive types, arrays, structs, and even other ADTs:

type widget struct {
	foo string
	bar string
}

type Option[T] {
	Some(T)
	None
}

func main() {
	// All of the following are valid:
	var optInt Option[int] = Some(10)
	var optWidget Option[Widget] = Some(widget{"foo", "bar"})
	var optOptWidget Option[Option[Widget]] = Some(Some(widget{"foo", "bar"}))
	var emytyOptOptWidget Option[Option[Widget]] = Some(None)
}