Summary of the Practical Type System (PTS) Article Series

First Published



Christian Neumanns


Tristano Ajmone


CC BY-ND 4.0

Three Types


This article provides a brief summary of the article series How to Design a Practical Type System to Maximize Reliability, Maintainability, and Productivity in Software Development Projects.


This summary is updated each time a new article in the series has been published.

1. What, Why, and How?

Benefits of a practical type system:

  • increased expressiveness

  • more reliable and maintainable code

  • better support in IDEs and tools

  • more generic, reusable code

  • more efficient code

  • support for the First data, then code! approach

The cardinality of a type is the number of distinct values in its set of values. For example, type boolean has a cardinality of 2, because there are two values: true and false.

To achieve its goal (reliability, maintainability and productivity), PTS is founded on two important rules:

  • A practical type system must fulfill two conditions:

    • The Easy! Condition

      It is easy to:

      • Define new types with the lowest possible cardinality.

      • Quickly understand types by looking at their definition in the source code.

      • Change and maintain types.

    • The Fail Fast! Condition

      Invalid values, type incompatibilities, and other type-related errors are detected immediately at compile-time, whenever possible.

      If it's impossible to detect an error at compile-time then it must be detected as soon as possible at run-time.

  • All data types in a software project should have the lowest possible cardinality.

2. Essence and Foundation

PTS Foundation

PTS is designed to maximize reliability, maintainability, and productivity in software development projects.

The Practical Type System (PTS) originated in the Practical Programming Language (PPL) — a compiled, high-level, object-oriented and functional programming language designed for application programming. PPL is a currently unmaintained prototype.

PTS is a set of ideas which I tried out and improved in PPL (a proof-of-concept implementation of PTS), found to be useful, and now want to share and discuss.

We need to differentiate between the PTS paradigm (set of features) and the PTS syntax. Both emerged from PPL, but they do not depend on each other. A programming language implementing the PTS paradigm could use a syntax which is very different from the PTS syntax shown in this article series.

PTS is not bound to a certain programming language paradigm. Most PTS concepts could be used, for example, in a procedural or functional programming language, or in a language designed for systems programming.

To maximize reliability and maintainability, a selected set of features is deliberately not supported in PTS. Some examples are:

  • Undefined state and undefined behavior

  • Silently ignored errors

  • Unsafe null-handling

  • Mutable data by default

  • Shared mutable data

  • Implicit type conversions/coercions

  • Truthy, falsy, and nullish values

  • Dynamic typing

  • Buffer overflows

  • Pointer arithmetics.

PTS provides the following core types:

  • Scalar types: character, string, number (integer and decimal), and boolean

  • Collections: list, set, and map

  • null (not Maybe / Option)

3. Record Types

Record Example

PTS provides the following key features for record types (aka structure, struct, or compound data):

  • Concise and clear syntax — without noise or code duplication

  • Immutability by default

  • Built-in support for easy and flexible data validation, both for individual attributes and for the whole record

  • Default values for attributes

  • Named attribute assignments to improve readability, reliability, and maintainability

  • Type parameters to write generic, type-safe code

  • Type inheritance

  • A with operator to facilitate creating record copies with some values modified

  • A to_string function for generating short, human-readable descriptions of record objects

  • Structured documentation

  • Support for serializing/deserializing record objects.

These features aim to simplify and streamline working with record types, while also increasing reliability, maintainability, and safety.

In particular, the combination of easy and flexible data validation and immutability by default make PTS record types very robust.

4. Union Types

Union Type Example

Without union types, an object reference (constant, variable, input parameter, etc.) is always an instance of a single type. For example, input parameter identifier is declared to be of type string.

Union types allow an object reference to be an instance of any type among a defined set of types. For example, input parameter identifier is declared to be of type string or number or null.

Union types provide the following benefits:

  • They are simple to understand, easy to use, and they provide an elegant solution for frequent programming tasks.

  • They provide a sound foundation for uniform null- and error-handling — two critical aspects of a practical type system.

    Here's an example of a function that might return a string or a file_error or null:

    fn read_text_file ( file file_path ) -> string or null or file_error
        // function body
  • They help to simplify APIs, increase type-safety, facilitate eager/lazy evaluation, and provide additional benefits.

PTS provides a set of dedicated operators and statements to streamline working with union types.

5. Null-Safety

PTS uses null to represent the absence of a value.

Union types are used to declare nullable object references (e.g. string or null).

Null-safety is natively built into the type system, therefore null pointer errors cannot occur.

PTS provides a set of dedicated operators and statements to facilitate null-handling as much as possible.

Flow typing reduces the number of null checks required in the code, which leads to smaller and faster code.

6. Error Handling

Error in the Error-Handling Code

Here's a brief summary of PTS error-handling rules and built-in support:

  • There are two types of errors:

    • anticipated errors (e.g. file_not_found_error, invalid_data_error)

    • unanticipated errors (e.g. out_of_memory_error, stack_overflow_error)

  • Anticipated errors:

    • are expected to possibly occur at run-time

    • must be declared as a function return type (using union types)

    • must be handled in one way or another, or explicitly ignored

  • Unanticipated errors:

    • are not expected to occur at run-time

    • can potentially occur at any time, and at any location in the source code

    • are either handled by one or more global error handlers or caught and handled explicitly anywhere in the source code

    • can be thrown implicitly or explicitly

  • A set of commonly used anticipated and unanticipated error types are defined in the standard library. Additional, domain-specific error types can be defined in a software development project, to cover specific needs.

  • PTS provides a set of dedicated operators and statements to facilitate error-handling.