Summary of the Practical Type System (PTS) Article Series

First Published

2023-10-17

Author

Christian Neumanns

Editor

Tristano Ajmone

License

CC BY-ND 4.0


Three Types

Introduction

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.

Note

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.

7. We Should Eradicate the Empty String — Here's Why

Empty String vs Null

Preventing immutable strings and collections from being empty provides the following advantages:

  • More reliable code because more bugs can be found at compile-time. The compiler reminds us to handle edge cases we might easily overlook.

  • Simpler and less error-prone code because there is no need to test for "null or empty".

  • Simpler and less error-prone APIs for non-empty, immutable string and collection types (e.g. element_count never returns zero, hence no risk of a division by zero).

These advantages are particularly valuable when working on large codebases.

The following approach is used in PTS:

  • Strings and collections are immutable and can't be empty.

  • Instead of empty strings/collections, null is used to represent "no elements".

  • Builders are used to build (create) immutable strings and collections.

  • PTS also provides mutable strings and collections that can be empty, but they are used rarely.