Toplevel Declarations

The following declarations are available at the toplevel of a Duo module.

Import statements

Other Duo modules can be imported using an import declaration. The following snippet imports the two modules Function and I64, which have to be declared in corresponding files Function.duo and I64.duo. These files have to be available in the library search path.

import Function;
import I64;

Circular module imports are not allowed and will generate an error. It is currently not possible to only import some of the declarations of a module, or to import a module qualified.

Type synonyms

Type synonyms can be defined using the type keyword. In the following snippet, a type synonym SBool is defined as an alias for the structural type < True, False >. For the typechecker the two types SBool and < True, False > are indistinguishable.

constructor True;                                                   
constructor False;                                                  
                                                                    
type SBool := < True, False >;  

Parameterized type synonyms are currently not yet implemented.

Type operators

Duo supports binary type operators with user-specified associativity and precedence. For example, the function type and the -> operator are specified in the following manner:

-- | The function type
codata Fun : (-a : CBV, +b : CBV) -> CBN {
    Ap(a,return b)
};

type operator -> rightassoc at 0 := Fun;

This first defines the codata type Fun and then introduces the arrow symbol as a custom notation for the Fun type with right associativity and precedence level 0. Every data and codata type has to be introduced with a lexical identifier, Fun in this example, before a type operator can be introduced for the type. The right-hand side of a type operator declaration expects the name of a typeconstructor with arity 2. Nullary and unary type operators are currently not implemented.

Data and codata declarations

Constructor and destructor declarations for structural types

Structural types, i.e. polymorphic variants and structural records, require their respective constructors and destructors to be declared at the toplevel before they can be used. This is different to, for example, OCaml, where a backtick in front of the name of the constructor is used. This design was chosen for the following reasons:

  • Docstrings can be attached to the constructors at the declaration site, and these docstrings can be displayed on hover at the respective use sites.
  • Typos in the names of constructors of polymorphic variants can be detected early.
  • Since the evaluation of expressions in Duo is kind-directed, we need a place to specify how constructors and destructors of structural types are to be evaluated.

Constructor declarations

In order to define a structural variant of booleans, the following two declarations can be used.

constructor True;
constructor False;

In this case, no evaluation order has been specified, and in the case of constructors this defaults to CBV. I.e. the above declaration is a shorthand for the more explicit declaration:

constructor True : CBV;
constructor False : CBV;

For constructors which take arguments the user has to specify the evaluation order used for the respective argument position. Structural peano natural numbers where the argument to the successor constructor is evaluated eagerly are declared as follows:

constructor Z;
constructor S(CBV);

which, again, is shorthand for

constructor Z : CBV;
constructor S(CBV) : CBV;

These constructors then occur in structural data types like < True, False> or < S(< Z >)>.

Destructor declarations

Destructor declarations are similar to constructor declarations, with one difference. If you don't specify a evaluation order for the destructor, it defaults to CBN evaluation. Hence, the declaration

destructor Name(CBV);

is shorthand for the declaration

destructor Name(CBV) : CBN;

These destructors then occur in structural codata types like { Name(String) }.

Producer declarations

A producer is declared using the def prd syntax. An optional type signature can be specified.

def prd two := S(S(Z));

def prd three: Nat := S(S(S(Z)));

def prd id: forall a. a -> a := \x => x;

By default, producers defined using the def prd keywords are not recursive. For recursive definitions, the keyword rec has to be added.

Consumer declarations

A consumer is declared using the def cns syntax. An optional type signature can be specified.

def cns exitOnBool := case {
    True => #ExitSuccess,
    False => #ExitFailure
};

def cns exitOnBool : Bool := case {
    True => #ExitSuccess,
    False => #ExitFailure
};

By default, consumers defined using the def cns keywords are not recursive. For recursive definitions, the keyword rec has to be added.

Command declarations

A command is declared using the def cmd syntax. Commands have no type, and it is therefore not possible to write a type signature.

def cmd exit := #ExitFailure;

def cmd main := #ExitSuccess;

The command called "main" is treated specially, since execution starts with the execution of the "main" command.

Type Class declarations

A type class is declared using the class keyword. Currently only single parameter type classes are supported, thus a single type variable together with its kind and variance have to be declared.

class Show(+a : CBV){
    Show(a, return String)
};

Inside the body of the class declaration an arbitrary number of type class methods can be defined. The signature has to respect the variance of the type variable above. Method and type class name live in different name spaces, so it is possible to overload them.

Instance declarations

An instance of a type class is declared using the instance keyword. Unlike in languages such as Haskell, instances have to be named.

instance showBool : Show Bool {
    Show(b, k) => case b of {
        True => MkString("True"),
        False => MkString("False")
    } >> k
};

Type class methods are implemented as commands and can be used as such, e.g.:

def cmd main := Show(True, mu x. #Print(x, #ExitSuccess));