In LF, deductive systems are represented by signatures consisting of constant declarations. Twelf implements declarations in a straightforward way and generalizes signatures by also allowing definitions, which are semantically transparent. Twelf currently does not have module-level constructs so that, for example, signatures cannot be named. Instead, multiple signatures can be manipulated in the programming environment using configurations (see section 9.1 Configurations).

The LF type theory which underlies LF is stratified into three levels:
objects *M* and *N*, types *A* and *B*, and kinds
*K*. Twelf does not syntactically distinguish these levels and
simply uses one syntactic category of term. Similarly, object-level
constants *c* and type-level constants *a* as well as
variables share one name space of identifiers.

In explanations and examples we will use letters following the
mathematical conventions above to clarify the roles of various terms.
We also use *U* and *V* to stand for arbitrary terms.

- Grammar: The grammar of Twelf
- Constructor Declaration: Introducing types and object constructors
- Definitions: Defining abbreviations
- Operator Declaration: Infix, prefix and postfix operators
- Name Preferences: Assigning names to anonymous variables
- Sample Signature: Example of First-Order Logic

The grammar below defines the non-terminals `sig`

, `decl`

,
`term`

and uses the terminal `id`

which stands for identifers
(see section 2.2 Identifiers). The comments show the meaning in LF. There are
various special declarations `%`

such as `keyword``%infix`

or `%theorem`

which are omitted here and detailed in the
appropriate sections.

sig ::= % Empty signature | decl sig % Constant declaration decl ::= id : term. %a : Korc : A| id : term = term. %d : A = M| id = term. %d = M| _ : term = term. % anonymous definition, for type-checking | _ = term. % anonymous definition, for type-checking | %infix ixdecl. % operator declaration | %prefix pxdecl. % operator declaration | %postfix pxdecl. % operator declaration | %name id id. % name preference declaration | %query qdecl. % query declaration | %solve id : term. % solve declaration | %mode mdecl. % mode declaration | %terminates tdecl. % termination declaration | %theorem thdecl. % theorem declaration | %prove pdecl. % prove declaration term ::= type %type| id % variablexor constantaorc| term -> term %A->B| term <- term %A<-B, same asB->A| {id : term} term %Pi x:A. KorPi x:A. B| [id : term] term %lambda x:A. Borlambda x:A. M| term term %A MorM N| term : term % explicit type ascription | _ % hole, to be filled by term reconstruction | {id} term % same as`{id:_}`

term | [id] term % same as`[id:_]`

term

The constructs `{x:U} V`

and `[x:U] V`

bind the identifier
`x`

in `V`

, which may shadow other constants or bound
variables. As usual in type theory, `U -> V`

is treated as an
abbreviation for `{x:U} V`

where `x`

does not appear
in `V`

. However, there is a subtlety in that the latter allows
an implicit argument (see section 4.2 Implicit Arguments) to depend on
`x`

while the former does not.

In the order of precedence, we disambiguate the syntax as follows:

- Juxtaposition (application) is left associative and has highest precedence.
- User declared infix, prefix, or postfix operators (see below).
``->'`is right and``<-'`left associative with equal precedence.``:'`is left associative.``{}'`and``[]'`are weak prefix operators.

For example, the following are parsed identically:

d : a <- b <- {x} c x -> p x. d : ({x} c x -> p x) -> b -> a. d : ((a <- b) <- ({x:_} ((c x) -> (p x)))).

New type families or object constructors can be introduced with

condec ::= id : term. %a : Korc : A

Here `a`

stands for a type family and `K`

for its kind,
whereas `c`

is an objects constructor and `A`

its type.
Identifiers are resolved as follows:

- Any identifier
`x`

may be bound by the innermost enclosing binder for`x`

of the form`{x:A}`

or`[x:A]`

. - Any identifer which is not explicitly bound may be a declared or defined constant.
- Any uppercase identifier, that is, identifier
starting with
``_'`(underscore) or an upper case letter, may be a free variable. Free variables are interpreted universally and their type is inferred from their occurrences (see section 4 Term Reconstruction). - Any other undeclared identifier is flagged as an error.

Twelf supports notational definitions, currently employing a restriction to allow a simple and efficient internal treatment. Semantically, definitions are completely transparent, that is, both for type checking and the operational semantics definitions may be expanded.

defn ::= id : term = term. %d : A = M| id = term. %d = M

where the second is equivalent to `id : _ = term`

. Definitions
can only be made on the level of objects, not at the level of type
families because the interaction of such definitions with logic
programming search has not been fully investigated.

In order to avoid always expanding definitions, Twelf currently only
permits strict definitions (see section 4.4 Strict Definitions). A definition
of a constant `c`

is strict if all arguments to `c`

(implicit
or explicit) have at least one strict occurrence (see section 4.3 Strict Occurrences) in the right-hand side of the definition, and the
right-hand side contains at least one constant. In practice, most
notational definitions are strict. For some examples, see section 3.6 Sample Signature and section 4.4 Strict Definitions.

The power of definitions in Twelf, however, is severely limited by the lack of recursion. It should only be thought of as notational definition, not as a computational mechanism. Complex operations need to be defined as logic programs, taking advantage of the operational semantics assigned to signatures (see section 5 Logic Programming).

The user may declare constants to be infix, prefix, or postfix
operators. Operator precedence properties are associated with
constants, which must therefore already have been declared with a type
or kind and a possible definition. It is illegal to shadow an infix,
prefix, or postfix operator with a bound variable. We use
`nat`

for the terminal natural numbers.

assoc ::= none % not associative | left % left associative | right % right associative prec ::= nat % 0<=prec<10000 ixdecl ::= assoc prec id pxdecl ::= prec id decl ::= ... | %infix ixdecl. | %prefix pxdecl. | %postfix pxdecl.

During parsing, ambiguous successive operators of identical precedence
such as `a <- b -> c`

are flagged as errors. Note that it is not
possible to declare an operator with equal or higher precedence than
juxtaposition or equal or lower precedence than ``->'` and ``<-'`.

During printing, Twelf frequently has to assign names to anonymous variables. In order to improve readability, the user can declare a name preference for anonymous variables based on their type. Thus name preferences are declared for type family constants. Note that name preferences are not used to disambiguate the types of identifiers during parsing.

decl ::= ... | %name id id.

Following our same conventions, a name preference declaration has the
form `%name `

, that is, the first identifier must
be a type family already declared and the second is the name preference
for variables of type `a` `id``a`. The second identifier must be uppercase,
that is, start with a letter from ``A'` through ``Z'` or an
underscore ``_'`. Anonymous variables will then be named

, `id`*1*

, etc.
`id`*2*

Below is a signature for intuitionistic first-order logic over an
unspecified domain of individuals and atomic propositions. It
illustrates constant declarations and definitions and the use of
operator precedence and name preference declarations. It may be found
in the file ``examples/guide/nd.elf'`.

%%% Individuals i : type. %name i T %%% Propositions o : type. %name o A imp : o -> o -> o. %infix right 10 imp and : o -> o -> o. %infix right 11 and true : o. or : o -> o -> o. %infix right 11 or false : o. forall : (i -> o) -> o. exists : (i -> o) -> o. not : o -> o = [A:o] A imp false. %%% Natural Deductions nd : o -> type. %name nd D impi : (nd A -> nd B) -> nd (A imp B). impe : nd (A imp B) -> nd A -> nd B. andi : nd A -> nd B -> nd (A and B). ande1 : nd (A and B) -> nd A. ande2 : nd (A and B) -> nd B. truei : nd (true). % no truee ori1 : nd A -> nd (A or B). ori2 : nd B -> nd (A or B). ore : nd (A or B) -> (nd A -> nd C) -> (nd B -> nd C) -> nd C. % no falsei falsee : nd false -> nd C. foralli : ({x:i} nd (A x)) -> nd (forall A). foralle : nd (forall A) -> {T:i} nd (A T). existsi : {T:i} nd (A T) -> nd (exists A). existse : nd (exists A) -> ({x:i} nd (A x) -> nd C) -> nd C. noti : (nd A -> nd false) -> nd (not A) = [D:nd A -> nd false] impi D. note : nd (not A) -> nd A -> nd false = [D:nd (not A)] [E:nd A] impe D E.

Go to the first, previous, next, last section, table of contents.