• Nenhum resultado encontrado

3.4. PROVING AND PROGRAMMING IN DEDUKTI

In Dedukti, there is no xed notion of constructor so we do not need to distinguish diff from Diff. The invariant automatically holds for close normal terms:

nat : Type. 0 : nat .

S : nat -> nat . int : Type.

def Diff : nat -> nat -> int .

[m,n] Diff (S m) (S n) --> Diff m n.

3.4.2 Partial Functions

Usually in type theory (for example in the Calculus of Inductive Constructions, in Martin-Löf Type Theory or in NuPRL), symbols can be classied into: constructors, type constructors, functions, and axioms.

Constructors are used to build values, type constructors are used to build types, func- tions are abbreviations for their denitions, and axioms are symbols assumed to inhabit their types without justication and should be avoided if possible. Constructors, type constructors and axioms have no associated reduction behaviour. Functions however can always be unfolded. In type theory, functions are total.

In the λΠ-calculus modulo however, only one kind of symbol is considered. Dedukti distinguishes static and denable symbols but this is a dierent scenario since if f is a denable symbol, Dedukti does not enforce that f actually appears as head symbol of some rewrite rules and as we have seen, some denable symbols play the role of smart constructors.

Dedukti does not enforce that denitions are total because this has no meaning in the λΠ-calculus modulo: types can always be extended by declaring new symbols:

Example 6. Consider again the usual signature dening Peano natural numbers:

nat : Type. 0 : nat .

S : nat -> nat .

In this signature, we can dene Peano addition as usual:

def plus : nat -> nat -> nat . [n] plus 0 n --> n

3.4. PROVING AND PROGRAMMING IN DEDUKTI

[m,n] plus (S m) n --> S ( plus m n).

This denition is total in the sense that any normal close term of type nat is either 0 or starts with S.

However, we can extend the type nat by declaring a new constructor infty representing innity:

infty : nat .

and now the plus function is partial and the term plus infty 0 is normal.

In Dedukti, the distinction between constructors, axioms, total functions and partial functions is only in the eye of the user. It is not always possible to split the set of symbols between constructors and total functions.

Example 7. The following signature denes lists of natural numbers:

list : Type. Nil : list .

Cons : nat -> list -> list .

The functions returning the head and the tail of a constructed list can be partially dened:

def head : list -> nat . def tail : list -> list . [a] head ( Cons a _) --> a.

[l] tail ( Cons _ l) --> l.

To summarize, there is almost only one kind of symbol in Dedukti; constructors, axioms, and functions are not distinguished; some symbols never reduce, some other reduce on any closed normal terms, but some other sometimes reduce and sometimes do not; smart constructors and partial functions belong to this category.

3.4.3 Encoding Polymorphism

Polymorphism is the ability to dene functions acting on several types; two kinds of polymorphism can be distinguished, parametric polymorphism and ad-hoc polymorphism.

3.4. PROVING AND PROGRAMMING IN DEDUKTI

Parametric polymorphic functions are functions whose denitions are generic in one or more types; for example, the identity function can be dened using parametric poly- morphism as λA:T ype. λx:A. x. The λΠ-calculus and the λΠ-calculus modulo do not feature polymorphism1 but:

ˆ Dedukti has an option, "-coc" which turns Dedukti into a type-checker for the Cal- culus of Constructions modulo, a very small adaptation of the λΠ-calculus modulo featuring parametric polymorphism.

ˆ Without the "-coc" option, polymorphism can easily be encoded. In order to pass types as arguments, we need to reify types as terms of a xed type type and interpret them as types by an injection term. We need to construct products in type so we introduce the constant pi for this purpose and we add a rewrite rule identifying interpretations of products with products of interpretations:

type : Type.

def term : type -> Type.

pi : A : type -> ( term A -> type ) -> type .

[A,B] term (pi A B) --> x : term A -> term (B x).

The identity function can then be dened by:

def id (A : type ) (x : term A) := x.

Ad-hoc polymorphism is the ability for a function to act dierently depending on the type of its argument. Ad-hoc polymorphism is usually dened by overloading a function symbol with several types and denitions. For example, we might want to dispose of a polymorphic equality eq : A : type -> term A -> term A -> term boolwhose denition depends on its type argument:

bool : type . True : term bool . False : term bool . nat : type .

0 : term nat .

S : term nat -> term nat .

1This comes from the condition Γ A : Type in rule (Abs) of Section 3.2. In particular, the term λA:T ype. λx:A. xis not well-typed.

3.4. PROVING AND PROGRAMMING IN DEDUKTI

def plus : term nat -> term nat -> term nat . [n] plus 0 n --> n

[m, n] plus (S m) n --> S ( plus m n).

int : type .

def Diff : term nat -> term nat -> term int . [m, n] Diff (S m) (S n) --> Diff m n.

def eq : A : type -> term A -> term A -> term bool . [] eq nat 0 0 --> True

[] eq nat 0 (S _) --> False [] eq nat (S _) 0 --> False

[m, n] eq nat (S m) (S n) --> eq nat m n [m1 ,m2 ,n1 ,n2]

eq int ( Diff m1 m2) ( Diff n1 n2) -->

eq nat ( plus m1 n2) ( plus n1 m2) [] eq bool True True --> True

[] eq bool True False --> False [] eq bool False True --> False [] eq bool False False --> True .

Given our encoding of parametric polymorphism, ad-hoc polymorphism is the same as partial denitions for functions on type type, whereas parametric polymorphism corre- sponds to total denitions for functions on type type.

3.4.4 Overfull Denitions

The dual feature to partial denition is overfull denition, that is dening a total function with more rules than needed to make it total. This seemingly useless feature actually provides an elegant solution to a common issue in type theory: we cannot state that the empty vector is a neutral element for vector concatenation.

Example 8. We can dene vectors of natural numbers (lists of numbers depending on their length) and concatenation:

vector : nat -> Type. Nilv : vector 0.

Consv : n : nat -> nat -> vector n -> vector (S n).

def append : m : nat -> n : nat -> vector m -> vector n -> vector ( plus m n).

[v] append _ _ Nilv v --> v

[m,n,a,v,w] append _ n ( Consv m a v) w -->

3.4. PROVING AND PROGRAMMING IN DEDUKTI

Consv ( plus m n) a ( append m n v w).

but form : natandv : vector m, the term{append m 0 v Nilv} has typevector (plus m 0) which is not convertible tovector mso we cannot state that append m 0 v Nilv is equal to v.

This can be xed in Dedukti by adding the rewrite rule [m]plus m 0 --> m.

In fact, the denition of equality of integers that we gave in Section 3.4.3 is not conuent:

the following counterexample is given by CSIHO:

eq int (Diff (S n1) (S n2)) (Diff n3 n4)

eq int (Diff n1 n2) (Diff n3 n4)

eq nat (plus n1 n4) (plus n3 n2)

eq nat (plus (S n1) n4) (plus n3 (S n2))

eq nat (S (plus n1 n4)) (plus n3 (S n2))

This can be xed by adding the rewrite rule [m,n]plus m (S n) --> S (plus m n) which leads to the fully symmetric denition of plus:

def plus : nat -> nat -> nat . [n] plus 0 n --> n

[m] plus m 0 --> m

[m,n] plus (S m) n --> S ( plus m n) [m,n] plus m (S n) --> S ( plus m n).

Overfull denitions also naturally appear when translating problems and proofs from Deduction modulo: the more equality axioms are turned into rewrite rules, the simpler and shorter the proof will be so there is no reason to stop when the dened function is total.

Moreover, when we add a rewrite rule such as [m]plus m 0 --> m for a total symbol such as our rst denition ofplus, the conuence condition which is automatically checked by CSIHO guarantees that the reduction relation on ground terms is unchanged. Actually, checking that critical pairs generated by the new ruleplus 0 0andplus (S m) 0are closed is the biggest part of a proof of∀m.plus m 0=m. We are actually delegating some reasoning to the conuence checker, and it might even provide counter-examples when it fails!

3.4. PROVING AND PROGRAMMING IN DEDUKTI

For example, if we mistakenly dene addition of integers by def int_plus : int -> int -> int .

[m1 , n1 , m2 , n2]

int_plus ( Diff m1 n1) ( Diff m2 n2) -->

Diff ( plus m1 n1) ( plus m2 n2 ).

then CSIHO catches the error and provides the following explanation:

int_plus (Diff (S n1) (S n2)) (Diff n3 n4)

int_plus (Diff n1 n2) (Diff n3 n4)

Diff (plus n1 n2) (plus n3 n4)

int_plus (plus (S n1) (S n2)) (plus n3 n4)

int_plus (S (S (plus n1 n2))) (plus n3 n4)

Thanks to the symmetric denition of plus, we can state that Nilvis a neutral element for append but we get almost the same level of condence by adding the rewrite rule [v]append _ _ v Nilv --> vand requiring a conuence check.

3.4.5 Meta-Programming

Programmers often feel the need of dening syntactic sugar over a programming lan- guage such as dening a for loop as syntactic sugar around a while loop.

These denitions of syntactic sugar cannot be achieved by regular function denitions because they have to be performed before evaluation of arguments; they manipulate code snippets, not regular values.

The simplest solution is to add a preprocessor: a programming language designed for manipulating the programs of the initial language. The preprocessing phase happens before the program is evaluated or compiled.

Some languages such as LISP are their own preprocessors, this is known as meta- programming. In meta-programming, two or more evaluation phases can be distinguished, parts of the semantics of the language are available in certain phases only while others are available in all steps.

3.4. PROVING AND PROGRAMMING IN DEDUKTI

Rewriting is known to be a nice framework for meta-programming, the Pure program- ming language [88], a dynamically-typed language based on rewriting and the Maude sys- tem [52], an implementation of rewriting logics achieve meta-programming by reection:

declarations and rewrite rules are represented as rst-class objects which can be manipu- lated in the language.

In Dedukti, rewrite rules are not rst-class objects so it is not clear if we can achieve reection simply. Meta programming in Dedukti is however easy by chaining Dedukti invocations.

If two rewrite systemsR1andR2 are dened on the same signature, we can ask Dedukti to normalize a term t with respect to R1 (using the command #SNF). The output of Dedukti is a term in Dedukti syntax so we can check it with respect toR2.

This process can be iterated, tcan be normalized tot1 with respect toR1, thent1 can be normalized to t2 with respect to R2, then t2 can be normalized to t3 with respect to some other rewrite system R3 and so on. The term tn−1 resulting from this process can nally be checked in the nal rewrite systemRn.

Interestingly, the intermediate systems (R1, . . . ,Rn−1) need not a degree of condence as high as the last oneRn. In particular, we will often consider non-linear and non-conuent intermediate systems, this is not a problem as long as we do not break subject reduction.

These unsafe intermediate systems are useful to model non-conuent behaviour but also for eciency reasons. For example, it is very tempting to dene polymorphic equality by the following non-linear rewrite system:

bool : type . True : term bool . False : term bool .

def eq : A : type -> term A -> term A -> term bool . [x] eq _ x x --> True

[x,y] eq _ x y --> False .

but we usually avoid this denition because it is not conuent, even without β- reduction: the term eq A x x reduces to both True and False. At the meta-level however, we take the liberty of accepting this kind of rewrite systems. It gives a direct access to the conversion check inside the language. Using the rewrite system of Section 3.4.3, the time