On s'intéresse aux fonctions définies sur l'ensemble
On construit les fonctions récursives primitives de proche en proche en partant des trois fonctions de base :
et en itérant les deux constructions suivantes :
Les fonctions récursives primitives se programment dans tout langage de programmation, à l'aide d'une simple instruction itérative for :
Il n'y a pas de boucles while et le calcul des fonctions récursives primitives se termine toujours.
predecesseur(0) = 0
predecesseur(Succ(x)) = x
On utilise ici la définition récursive de predecesseur en prenant n=0, g la fonction identiquement nulle, h(x,y) = x projection sur la première composante.
somme(0,y) = y
somme(Succ(x),y) = Succ(somme(x,y))
On utilise ici la définition récursive de somme en prenant n=1, g(y) = y, h(x,z,y) = Succ(z), composée de la fonction Successeur et de la projection sur la deuxième composante.
Plus généralement, si
produit(0,y) = 0
produit(Succ(x),y) = somme(y,produit(x,y))
On utilise ici la définition récursive de produit en prenant n=1, g identiquement nulle, h(x,z,y) = somme(y,z), composée de la fonction somme déjà définie et de deux projections.
Plus généralement, si
Il s'agit de la fonction valant 0 pour x = 0 et 1 pour x > 0.
sg(0) = 0
sg(Succ(x)) = 1
On a pris n = 0, g nulle, h égale à la constante 1.
La différence tronquée de y par x vaut y-x si x < y et 0 sinon.
soustrait(0,y) = y
soustrait(Succ(x),y) = predecesseur(soustrait(x,y))
On a pris g(y) = y et pour h la fonction predecesseur déjà définie appliquée sur sa deuxième composante.
Il en résulte que la valeur absolue | x - y | = soustrait(x,y) + soustrait(y,x) est également récursive primitive.
Il en est de même de max(x,y) = x + soustrait(x,y) et de min(x,y) = soustrait(max(x,y),x+y).
A tout prédicat défini sur
On dira que le prédicat P est récursif primitif lorsque sa fonction caractéristique est récursive primitive.
On peut montrer que, si deux prédicats P et Q sont récursifs primitifs, il en est de même des prédicats (non P), (P et Q), (P ou Q), (P implique Q).
Le prédicat x < y est récursif primitif, puisque sa fonction caractéristique est égale à sg(soustrait(x,y)), qui est une fonction récursive primitive.
Sont également récursifs primitifs les prédicats suivants :
Si
En effet, si
Il est très important de borner la quantification par un nombre n. En effet, les prédicats
Ainsi, le prédicat "il existe un nombre parfait impair inférieur à n" est récursif primitif, mais pas le prédicat "il existe un nombre parfait impair". On ignore d'ailleurs (en 2006) la valeur de vérité de ce dernier prédicat.
Si
est récursive primitive.
En effet, si
Là aussi, il est important de borner la recherche du minimum. La fonction cherchant le plus petit x vérifiant
Ainsi, la fonction cherchant le plus petit nombre parfait impair inférieur à n (ou n+1 s'il n'existe pas) est une fonction récursive primitive de n. Mais la fonction donnant le plus petit nombre parfait impair n'est pas récursive primitive.
Une première limitation de la récursion primitive intervient dans les algorithmes susceptibles de ne pas se terminer. Tel est le cas de la quantification non bornée ou de la minimisation non bornée, vues précédemment.
Mais il ne suffit qu'une fonction soit définie récursivement, et par un procédé se terminant pour toute valeur des données, pour que la fonction soit récursive primitive. L'ensemble des fonctions récursives primitives n'est en effet qu'une partie de l'ensemble des fonctions récursives. Ainsi, la fonction d'Ackermann définie par
n'est pas récursive primitive car la récursion se fait sur deux entiers simultanément. Pourtant la définition doublement récursive de cette fonction permet en théorie de calculer sa valeur pour tout couple (n,p) d'entiers.
Le même problème se pose si on veut utiliser cet algorithme du minimum :
Bien que la fonction minimum soit récursive primitive, ce n'est pas la définition précédente qui permet de le montrer.
Pour pouvoir réaliser ces programmes on doit passer un système plus puissant, comme le Système T de Gödel par exemple.
Nous avons vu, dans le paragraphe Limites de la récursion primitive, deux exemples de définitions récursives qui ne sont pas récursives primitives :
Se pose alors la question suivante. Etant donnée une fonction, définie récursivement ou par un algorithme quelconque, est-il possible de déterminer par un processus automatique si cette fonction est récursive primitive ? Est-il possible de savoir si sa définition peut être modifiée pour ne faire appel qu'à la récursion primitive ? La réponse à cette question est négative. Il n'existe aucune procédure permettant de dire si une fonction est récursive primitive ou non. On dit que la détermination du caractère récursif primitif d'une fonction définie par un algorithme est indécidable. C'est un cas particulier du théorème de Rice, qui définit toute une classe de questions indécidables.
Nous pouvons donner schématiquement le raisonnement conduisant à cette conclusion. Les fonctions définies par un algorithme peuvent être numérotées par ordre croissant, au moyen d'un codage numérique de l'algorithme ou de la machine de Turing qui les définit. On appellera φn la n-ème fonction ainsi définie.
Raisonnons par l'absurde et supposons qu'il existe une procédure RP s'appliquant à l'algorithme définissant une fonction f (ou à l'entier n tel que f = φn) et qui vaut 1 si f est récursive primitive et 0 sinon. Notons RP(f) cette valeur 0 ou 1. Considérons alors, pour chaque entier n la fonction gn de x définie par :
Si l'algorithme de la fonction φn se termine par une valeur quelconque lorsqu'on l'applique à l'entier n lui-même, alors gn est identiquement nulle. Elle est alors récursive primitive, et RP(gn) = 1. Par contre, si l'algorithme de la fonction φn se met à boucler indéfiniment lorsqu'on l'applique à l'entier n lui-même, alors le calcul de gn(x) ne se termine pas. gn n'est pas récursive primitive, et RP(gn) = 0. On a donc :
Considérons enfin la fonction C définie par l'algorithme suivant :
La fonction C fonctionne comme suit : si RP(gn) = 0, alors C(n) retourne la valeur 0. Mais si RP(gn) = 1, alors C(n) entre dans une boucle dont elle ne ressort pas, et l'algorithme ne se termine pas. Autrement dit :
C étant défini par algorithme possède un rang c tel que C = φc. L'équivalence ci-dessus s'écrit alors :
Que se passe-t-il lorsqu'on donne à n la valeur c ? L'équivalence devient :
ce qui est absurde.
Aboutissant à une contradiction, on en conclut que la procédure RP ne peut exister.
Considérons la fonction définie comme suit, pour n>0 :
On ignore si, pour tout n, la boucle while se termine ou si, au contraire, il existe un entier n pour lequel le programme boucle indéfiniment. La conjecture de Syracuse postule que c'est le premier cas qui se produit. Il en résulterait alors que la fonction Collatz est la fonction constante égale à 1, et donc récursive primitive. Mais on est actuellement dans l'incapacité de prouver cette assertion.
La récursion primitive est un langage de programmation théorique qui a la propriété que tous les programmes écrits dans ce langage terminent. Pour pouvoir écrire des programmes dans ce langage on peut utiliser différents formalismes.
Les termes de Martin-Löf sont soit :
Dans la récursion, t est un terme sur lequel on fait la récursion, b est le cas de base et s l'étape de récurrence. Dans l'étape de récurrence, la variable x représente le prédécesseur de l'entier sur lequel on fait la récurrence et le y est l'étape de récurrence.
L'addition de n et p s'écrit Rec(n,p,(x,y)Succ(y)).
Kleene a introduit les combinateurs qui sont une autre manière de représenter les fonctions récursives primitives. Les combinateurs sont munis d'arité, c'est-à-dire qu'ils ont un nombre de paramètres défini (sauf dans un cas particulier).
Un combinateur est :
L'addition se programme ainsi : Rec(π11, S(Succ,π23))