Skip to content

Defining Data Types in Juvix

A crucial aspect of any programming language is the ability to define custom data types. In Juvix, these are known as inductive types. An inductive type is a type with elements constructed from a finite set of constructors.

Consider as a first example where we define a data type Bool with two constructors, true and false.

type Bool :=
  | true : Bool
  | false : Bool;

Syntax of Data Type Declaration

General Declaration

The declaration of a data type in Juvix consists of the keyword type, followed by a unique name, optional type parameters, and constructors.

type <name> <type-parameters> :=
    | <constructor1> : <type1>
    | ...
    | <constructorn> : <typen>;

In this syntax:

  • <name> represents a unique name for the declared data type.
  • <type-parameters> denote optional type parameters in the form A B C ... or with typing information (A : Type). These parameters define the return type of the constructors, i.e., <name> <type-parameters>.
  • <constructor1> through <constructorn> are the constructors of the data type. Each constructor has a unique name and a type, which can be the type of the declared data type or a function type from the types of the arguments to the type of the declared data type.

While there are variations in the syntax for declaring a data type (see the ADT syntax and record syntax), the most general syntax is the one outlined above.

Note

A data type declaration implicitly declares a module with the same name as the data type, containing the symbols of the constructors, and the type of the data type itself. One can open this module to access these symbols or hide it to prevent access to them.

Example of data types

The Unit type, the simplest data type, has a single constructor named unit.

type Unit := unit : Unit;

We then declare the Nat type, representing unary natural numbers. It introduces two constructors: zero and suc. For instance, suc zero represents one, while suc (suc zero) represents two.

type Nat :=
  | zero : Nat
  | suc : Nat -> Nat;

These constructors function as regular functions or patterns in pattern matching when defining functions. Here is an example of a function adding two natural numbers:

syntax operator + additive;

+ : Nat -> Nat -> Nat
  | zero b := b
  | (suc a) b := suc (a + b);

ADT syntax

As an alternative to the above syntax, we can use a more familiar and compact syntax for declaring data types. This syntax is inspired by ADT syntax in Haskell.

type <name> <type-parameters> :=
  | <constructor1> <arg1-1> ... <arg1-n>
  | ...
  | <constructorN> <argn-1> ... <argn-n>;

Different from the previous presentation, here the constructors do not have typing information.

If a type constructor as above has no arguments, then its type is the type of the data type being declared. In the case the type constructor has arguments, then its type is the function type from the types of the arguments to the type of the data type being declared.

For example, the Nat type can be declared as follows:

module Nat-ADT;
  type Nat :=
    | Z
    | S Nat;
end;

Another example is the List type, which is polymorphic in the type of its elements.

module List-ADT;
  type List A :=
    | Nil
    | Cons A (List A);
end;

Polymorphic data type

A data type can possess type parameters. When a data type has a type parameter A, it is referred to as polymorphic in A.

A classic example of this concept is the List type, which is polymorphic in the type of its list elements.

syntax operator :: cons;

type List (A : Type) :=
  | nil : List A
  | :: : A -> List A -> List A;

The following function determines whether an element is in a list or not.

module membership;
  import Stdlib.Data.Bool open using {Bool; false; ||};
  
  elem {A} (eq : A -> A -> Bool) (s : A) : List A -> Bool
    | nil := false
    | (x :: xs) := eq s x || elem eq s xs;
end;

For more examples of inductive types and how to use them, see the Juvix standard library.