Package ty

import ""

Overview ▾

Package ty provides utilities for writing type parametric functions with run time type safety.

This package contains two sub-packages `fun` and `data` which define some potentially useful functions and abstractions using the type checker in this package.


Go tip (or 1.1 when it's released) is required. This package will not work with Go 1.0.x or earlier.

The very foundation of this package only recently became possible with the addition of 3 new functions in the standard library `reflect` package: SliceOf, MapOf and ChanOf. In particular, it provides the ability to dynamically construct types at run time from component types.

Further extensions to this package can be made if similar functions are added for structs and functions(?).

func AssertType

func AssertType(v interface{}, t reflect.Type) reflect.Value

AssertType panics with a `TypeError` if `v` does not have type `t`. Otherwise, it returns the `reflect.Value` of `v`.

type A

type A TypeVariable

type B

type B TypeVariable

type C

type C TypeVariable

type D

type D TypeVariable

type E

type E TypeVariable

type F

type F TypeVariable

type G

type G TypeVariable

type TypeError

type TypeError string

TypeError corresponds to any error reported by the `Check` function. Since `Check` panics, if you want to run `Check` safely, it is appropriate to recover and use a type switch to discover a `TypeError` value.

func (TypeError) Error

func (te TypeError) Error() string

type TypeVariable

type TypeVariable struct {
    // contains filtered or unexported fields

TypeVariable is the underlying type of every type variable used in parametric types. It should not be used directly. Instead, use

type myOwnTypeVariable TypeVariable

to create your own type variable. For your convenience, this package defines some type variables for you. (e.g., `A`, `B`, `C`, ...)

type Typed

type Typed struct {
    // In correspondence with the `as` parameter to `Check`.
    Args []reflect.Value

    // In correspondence with the return types of `f` in `Check`.
    Returns []reflect.Type

    // The type environment generated via unification in `Check`.
    // (Its usefulness in the public API is questionable.)
    TypeEnv map[string]reflect.Type

Typed corresponds to the information returned by `Check`.

func Check

func Check(f interface{}, as ...interface{}) *Typed

Check accepts a function `f`, which may have a parametric type, along with a number of arguments in correspondence with the arguments to `f`, and returns inferred Go type information. This type information includes a list of `reflect.Value` in correspondence with `as`, a list of `reflect.Type` in correspondence with the return types of `f` and a type environment mapping type variables to `reflect.Type`.

The power of `Check` comes from the following invariant: if `Check` returns, then the types of the arguments corresponding to `as` are consistent with the parametric type of `f`, *and* the parametric return types of `f` were made into valid Go types that are not parametric. Otherwise, there is a bug in `Check`.

More concretely, consider a simple parametric function `Map`, which transforms a list of elements by applying a function to each element in order to generate a new list. Such a function constructed only for integers might have a type like

func Map(func(int) int, []int) []int

But the parametric type of `Map` could be given with

func Map(func(A) B, []A) []B

which in English reads, "Given a function from any type `A` to any type `B` and a slice of `A`, `Map` returns a slice of `B`."

To write a parametric function like `Map`, one can pass a pointer to a nil function of the desired parametric type to get the reflection information:

func Map(f, xs interface{}) interface{} {
	// Given the parametric type and the arguments, Check will
	// return all the reflection information you need to write `Map`.
	uni := ty.Check(
		new(func(func(ty.A) ty.B, []ty.A) []ty.B),
		f, xs)

	// `vf` and `vxs` are `reflect.Value`s of `f` and `xs`.
	vf, vxs := uni.Args[0], uni.Args[1]

	// `tys` is a `reflect.Type` of `[]ty.B` where `ty.B` is replaced
	// with the return type of the given function `f`.
	tys := uni.Returns[0]

	// Given the promise of `Check`, we now know that `vf` has
	// type `func(ty.A) ty.B` and `vxs` has type `[]ty.A`.
	xsLen := vxs.Len()

	// Constructs a new slice which will have type `[]ty.B`.
	vys := reflect.MakeSlice(tys, xsLen, xsLen)

	// Actually perform the `Map` operation, but in the world of
	// reflection.
	for i := 0; i < xsLen; i++ {
		vy := vf.Call([]reflect.Value{vxs.Index(i)})[0]

	// The `reflect.Value.Interface` method is how we exit the world of
	// reflection. The onus is now on the caller to type assert it to
	// the appropriate type.
	return vys.Interface()

Working in the reflection world is certainly more inconvenient than writing regular Go code, but the information and invariants held by `Check` provide a more convenient experience than how one normally works with reflection. (Notice that there is no error-prone type switching or boiler plate to construct new types, since `Check` guarantees the types are consistent with the inputs for us.)

And while writing such functions is still not so convenient, invoking them is simple:

square := func(x int) int { return x * x }
squared := Map(square, []int{1, 2, 3, 4, 5}).([]int)


There are a few restrictions imposed on the parametric return types of `f`: type variables may only be found in types that can be composed by the `reflect` package. This *only* includes channels, maps, pointers and slices. If a type variable is found in an array, function or struct, `Check` will panic.

Also, type variables inside of structs are ignored in the types of the arguments `as`. This restriction may be lifted in the future.

To be clear: type variables *may* appear in arrays or functions in the types of the arguments `as`.


Name      Synopsis
data      Package data tumbles down the rabbit hole into parametric data types.
fun      Package fun provides type parametric utility functions for lists, sets, channels and maps.