Vous êtes sur la page 1sur 35

Dan Garlasu, dgarlasu@yahoo.

com

1
Ordre du jour
1. Introduction
2. Évaluation des fonctions
3. Opérateurs de composition (construction)
4. Quelques problèmes de mise en œuvre
5. Calcul lambda

Course content available at:


coronet.iicm.tugraz.at/sa/scripts/lesson04.doc

2
 On se souvient que le paradigme de programmation impérative (procédurale) s'appuie
fortement sur la modification des valeurs d'une collection de variables, appelée l'état.
 Si l'on néglige les opérations d'entrée/sortie et la possibilité qu'un programme puisse
s'exécuter en continu, on peut arriver à l'abstraction suivante:
◦ Avant l'exécution, l'état a une valeur initiale représentant les entrées du programme, et lorsque le
programme est terminé, l'état a une nouvelle valeur incluant le(s) résultat(s)
◦ De plus, lors de l'exécution, chaque commande change d'état, ayant donc parcouru une séquence
finie de valeurs:
iS = S1 -> S2 -> … -> Sn -> fS
 L'état est généralement modifié par des commandes d'affectation impératives, telles que
v = E ou v := E
où v est une variable et E une expression
 Ces commandes peuvent être exécutées de manière séquentielle en les écrivant les unes
après les autres dans le programme, souvent séparées par un point-virgule

Par exemple dans un programme de triage, l'état comprend initialement un


tableau de valeurs (iS), et lorsque le programme est terminé, l'état a été modifié
de manière à ce que ces valeurs soient triées (fS), tandis que les états
intermédiaires représentent la progression vers ce but.

À propos de l'état du programme :


on a la fameuse instruction Push PSW
Quels sont les éléments de l'état d’un programme?
• compteur de programme,
• contenu des registres,
• adresse du pointeur de pile.

3
 Représente une rupture radicale avec le modèle précédent.
 Essentiellement, un programme fonctionnel est simplement une
expression, et l'exécution signifie l'évaluation de l'expression.
 En supposant qu'un programme impératif (dans son ensemble) est
déterministe, c'est-à-dire que la sortie est complètement
déterminée par l'entrée, nous pouvons dire que l'état final, ou les
fragments de celui-ci qui nous intéressent, est une fonction de l'état
initial, disons fS = f(iS).
 Dans la programmation fonctionnelle, cette vue est soulignée :
◦ le programme est en fait une expression qui correspond à la fonction
mathématique f . Les langages fonctionnels prennent en charge la
construction de telles expressions en permettant des constructions
fonctionnelles assez puissantes.

Applicabilité :
• Intelligence artificielle,
• Apprentissage automatisé,
• Résolution de problèmes.
• Même à des fins policières pour suivre des pistes en matière de criminalité.
• Traduction automatisée de texte ou jeu via l'apprentissage automatique,
• Robotique.
• Éditeur eMax sous Linux.

4
 Pour illustrer la distinction entre la programmation impérative et fonctionnelle,
la fonction factorielle pourrait être codée impérativement comme:
function factorial(n)
{
var x = 1;
while (n > 0)
{
x = x * n;
n = n 1;
}
return x;
}

 alors qu'elle s'exprimerait dans le paradigme fonctionnel, comme une


fonction récursive:
factorial n =
if n = 1 then 1
else n * factorial(n – 1);

LISP est une famille de langages de programmation avec une longue histoire et une notation de préfixe polonaise. Spécifié à
l'origine en 1958, Lisp est le deuxième langage de programmation de haut niveau le plus ancien largement utilisé aujourd'hui;
seul Fortran est plus ancien (d'un an). Comme Fortran, Lisp a beaucoup changé depuis ses débuts, et un certain nombre de
dialectes ont existé au cours de son histoire. Aujourd'hui, les dialectes Lisp à usage général les plus connus sont Common
Lisp et Scheme. Lisp a été créé à l'origine comme une notation mathématique pratique pour les programmes informatiques,
influencée par la notation du calcul lambda d'Alonzo Church. Il est rapidement devenu le langage de programmation privilégié
pour la recherche en intelligence artificielle (IA). En tant que l'un des premiers langages de programmation, Lisp a été le
pionnier de nombreuses idées en informatique, notamment les structures de données arborescentes, la gestion automatique
du stockage, le typage dynamique et le compilateur auto-hébergé.
Le nom LISP dérive de "LISt Processing". Les listes chaînées sont l'une des principales structures de données des langages
Lisp, et le code source Lisp est lui-même composé de listes. En conséquence, les programmes Lisp peuvent manipuler le
code source comme une structure de données, donnant naissance aux systèmes de macros qui permettent aux
programmeurs de créer une nouvelle syntaxe, ou même de nouveaux langages spécifiques à un domaine, intégrés dans
Lisp.
SLIP est un langage de programmation informatique de traitement de liste, inventé par Joseph Weizenbaum dans les années
1960. Le nom SLIP signifie Symmetric LIst Processor. Il a d'abord été implémenté comme une extension du langage de
programmation Fortran, puis intégré dans MAD et ALGOL.
SNOBOL (StriNg Oriented and symBOlic Language) est une série de langages de programmation informatique développés
entre 1962 et 1967 aux AT&T Bell Laboratories par David J. Farber, Ralph E. Griswold et Ivan P. Polonsky, aboutissant à
SNOBOL4. C'était l'un des nombreux langages orientés chaîne de texte développés dans les années 1950 et 1960;
Autres langages dans cette paradigme: Scheme, Haskell, R, Julia, Nimrod

5
 La principale caractéristique distinctive du paradigme de
programmation fonctionnelle est cependant la manière dont une
fonction est évaluée
 En termes simples, une forme normale peut être produite en
remplaçant les fonctions internes par leurs corps (les textes source)
 Exemple:
factorial n =
if n = 1 then 1
else n * factorial(n -1);
factorial(3) = 3* factorial(2) = 3*2*factorial(1) =3*2*1
 Autrement dit, l'expression 3*2*1 est une forme normale de
l'expression factorial(3)

Par exemple, dans le paradigme procédural, l'opérateur factorial (3) renvoie


toujours une valeur entière (6 dans ce cas particulier).
Conformément au paradigme fonctionnel, l'expression factorial (3) renvoie une
forme dite normale de la fonction « factorial ».

Si vous souhaitez effectuer des activités avec Big Data et Spark, il serait sage de
vous renseigner sur Scala, un langage de programmation fonctionnel. Scala
combine l'orientation objet avec la programmation fonctionnelle. Parce que Lisp
est un langage de programmation fonctionnel plus ancien, Scala pourrait être
appelé "Lisp rejoint le 21e siècle". Vous pouvez en savoir plus sur Scala sur
http://www.scala-lang.org/.

6
 De manière plus générale, un programme fonctionnel consiste en une
expression E (représentant à la fois l'algorithme et les arguments d'entrée).
Cette expression E est soumise à certaines règles de réécriture.
 La Réduction consiste à remplacer une partie P de E par une autre
expression P' selon les règles de réécriture données. ... Ce processus de
réduction sera répété jusqu'à ce que l'expression résultante n'ait plus de
parties qui pouvaient être réécrites.
 L'expression résultante E* est appelée la forme normale de E et constitue la
sortie du programme fonctionnel.
 Ainsi, par « la fonction E renvoie E* », nous entendons que E* est une forme
normale de E.
 Il existe également une fonction spéciale, eval, qui convertit les expressions
en valeurs
eval(factorial(3)) = eval (3*2*1) = 6

Common Lisp
Lisp est un langage de programmation intrinsèquement fonctionnel, mais la plupart des formes modernes de
Lisp ne sont plus purement fonctionnel. Il s'agit d'un thème récurrent dans les langages de programmation, car
cela n'a plus beaucoup de sens pour un langage de n'être bon que pour une seule chose directement.

R
R est un autre langage qui réside sur le spectre multi-paradigme. R est principalement un langage de
programmation fonctionnel, utilisé pour la modélisation statistique, mais tire bien sûr parti des génériques et de
la méthodologie qui lui permettent de faire plus que la plupart des langages fonctionnels typiques dont il pourrait
être difficile de profiter.

Julia
Julia est certainement langage dont il est difficile de parler à cet égard. Une chose qui est unique à propos de
Julia est la méthodologie derrière le langage lui-même. Cependant, sous des formes pures et basiques, Julia
est un langage de programmation fonctionnel. Bien sûr, comme R, Julia est un langage multi-paradigme, mais
prend également le sens du mot à un tout autre niveau avec une répartition multiple.

Rust
Rust est un autre excellent exemple de langage de programmation multi-paradigme. Bien que Rust ressemble
beaucoup à un C de plus haut niveau, il possède également de nombreuses propriétés qui seraient
généralement considérées comme fonctionnelles dans la définition, ce qui n'est pas une si mauvaise chose.

7
 La programmation fonctionnelle est basée sur le concept
mathématique d'une fonction et les langages de programmation
fonctionnelle comprennent au moins les éléments suivants :
◦ Ensemble de structures de données et fonctions associées
◦ Ensemble de fonctions primitives
◦ Ensemble d'opérateurs de composition (construction)
 Exemple: supposons qu'un langage fonctionnel supporte une
structure de données appelée List comme structure de données de
base.
◦ Exemples de listes : [1,5,6] [‘Nick’, ‘Thomas’, ‘Denis’}
◦ Les listes peuvent inclure d'autres listes en tant que membres :
◦ [1, [ ‘Nick’, ‘Thomas’, ‘Denis’],2, [25,36,72] ]
◦ Supposons aussi que nil représente une liste vide: nill = [ ]

8
 De plus, deux fonctions très simples seraient utiles:
◦ Fonction true() renvoie la valeur booléenne “true” si évaluée
◦ Fonction false() renvoie la valeur booléenne “false” si évaluée
 Parmi les fonctions intégrées (primitives) pour la
manipulation de liste figurent
◦ cons pour attacher un élément en tête (premier) de la liste
(concaténation),
◦ car pour extraire le premier élément d'une liste, et
◦ cdr qui renvoie une liste moins son premier élément

Examples:
CONS(‘Palina’ , [‘Nick’, ‘Thomas’, ‘Denis’]) = [‘Palina’, ‘Nick’, ‘Thomas’, ‘Denis’]
CONS([‘Nick’, ‘Thomas’, ‘Denis’], [25,36,72]) = [ [‘Nick’, ‘Thomas’, ‘Denis’],
25,36,72]
CAR([‘Nick’, ‘Thomas’, ‘Denis’]) = ‘Nick’
CDR([‘Nick’, ‘Thomas’, ‘Denis’]) = [ ‘Thomas’, ‘Denis’, ‘Palina’]
CONS(CDR(xList), CAR(xList)) = xList

9
 Supposons qu'il existe également un opérateur spécial COND.
L'opérateur peut être vu comme une liste composée d'un nombre fini
de paires d'éléments. Chaque paire comprend une condition et une
autre fonction. A l’exécution, on évalue la fonction utilisée comme
premier paramètre, et si elle renvoie la valeur booléenne "TRUE", le
second paramètre remplace l'opérateur COND.
 L'opérateur COND peut être vu sous la forme suivante:
COND{
{<condition_1>, <function_1>}
{<condition_2>, <function_2>}
......
{<condition_n>, <function_n>}
}

La fonction "function_k" remplace l'opérateur COND, si


l'évaluation de "condition_k" renvoie la valeur booléenne "TRUE" ;
toutes les conditions précédentes (c'est-à-dire les conditions <condition_1>,
<condition_2>, ..., <condition_n-1>) renvoient la valeur booléenne "FALSE".
Ainsi, l'opérateur COND est un exemple d'opérateur de composition
(construction).

10
 Une nouvelle fonction CHECK(list, element) renvoit (comme forme normale)
◦ true() si l'élément fait partie de la liste
◦ false() autrement
 peut être défini comme suit:
CHECK list, element
COND
{
{list = nill, false ()}
{CAR(list) = element, true()}
{true(), CHECK(CDR(list),element)}
}
 Example: la forme normale de la fonction CHECK([bca],a) est déduite:
CHECK([bca],a) = CHECK([ca],a) = CHECK([a],a) = true()

Voyons comment une forme normale de la fonction CHECK([bca],a) est déduite:


CHECK([bca],a) = CHECK([ca],a) = CHECK([a],a) = true()

11
Un autre exemple d'opérateur de composition (construction) est:
If <condition_1> then <function_1>
else <function_2>

Example:
CHECK list, element
If (list = nill) false()
else
{
if (CAR(list) = element) true()
else CHECK(CDR(list),element)
}

Expliquez l'exemple par l’utilization des fonctions CAR et CDR.

12
 Les types de données de liste sont assez importants pour tous les
langages de programmation basés sur la Paradigme Fonctionale.
 Par exemple, la liste [1,2,3] peut être implémentée comme suit:
Curseur, pointeur

 Par analogie, la liste [1, [‘Nick’, ‘Alex’], 3] peut être vue comme suit :

Éléments de la liste :
• Entête (Header)
• Curseur (Cursor)
• Aiguille (Pointer)

13
 C’est intéressant que les fonctions soient présentées sous la
même forme de liste. Le premier élément d'une telle présentation
de liste est appelé une tête et fait référence au corps de la fonction
de la même manière que d'autres éléments font référence (ou
peuvent faire référence) aux valeurs correspondantes

14
 Considérez la fonction mentionnée précédemment
factorial n =
if n = 1 then 1
else n * factorial(n - 1);
 Le processus de réduction de la fonction factorial(3) est illustré par
le schéma suivant:

En utilisant la notation
préfixée, la liste peut être
écrite comme suit :
[*, 3, [*, 2,1]]

Rappelez-vous que la fonction est soumise à certaines règles de réécriture qui


sont définies par le corps de la fonction.
Évidemment, le résultat de l'évaluation de la fonction factorial(3) est une valeur
unique 6.

15
 Nous avons dit que d'un point de vue théorique, la réduction des
expressions est une propriété fondamentale du Paradigme Fonctionnel.
Cependant, il a quelques défauts pratiques. Par exemple, considérons les
deux fonctions suivantes:
factorial n =
if n = 1 then 1
else n * factorial(n - 1);
--------------------------------
triple x =
x+x+x
 Une réduction normale de la fonction triple(factorial(3)) donne:
[+,[*,3,[*,2,1]], [*,3,[*,2,1]], [*,3,[*,2,1]] ]
 Par conséquent, le système doit évaluer trois instances distinctes de la
même expression [*, 3, [*, 2,1]]. Et ca, c'est inadmissible en pratique.

16
 Un autre problème peut être facilement détecté si on définit une nouvelle
fonction sumUp qui est censée s'appliquer à une liste de valeurs
arithmétiques et fait simplement l’addition de toutes les valeurs de la liste.
sumUp list =
COND
{
{list = nill, 0}
{true(),CAR(list) + sumUp(CDR(list))}
}
 La réduction de la fonction sumUp ([6]) donne [+, 6,0]. Dans le même
temps, la réduction de la fonction sumUp (factorial (3)) peut entraîner une
erreur d'exécution car la fonction sumUp sera appliquée à une liste se
présentant comme suit: [*,3,[*,2,1]].
 Ca aboutira à une forme normale qui ne peut pas être évaluée de manière
significative [+,*,sumUp(3,[*,2,1])]

17
 Il existe deux solutions principales aux problèmes
mentionnés, et ces solutions divisent le monde des
langages de programmation fonctionnels en deux
camps.
 Lazy Evaluation (call by need/appel par besoin)
 Eager Evaluation (applicative order/commande applicative)

Lazy Evaluation -> Évaluation paresseuse/tardive


Eager Evaluation -> Évaluation hâtive

18
 Il respecte la réduction de l'ordre normal, mais essaye d'optimiser
l'implémentation afin que plusieurs sous-expressions résultant de cette
manière soient partagées et ne soient jamais évaluées plus d'une fois.
 En interne, les expressions sont représentées sous forme de graphes
acycliques dirigés plutôt que sous forme d'arbres.
 Ceci est connu sous le nom d'évaluation «tardive» ou «appel par besoin»,
car les expressions ne sont évaluées que lorsque cela est absolument
nécessaire..

L’évaluation tardive conduit à des solutions élégantes pour des problèmes


impliquant des séries infinies et semble plus cohérente avec l'idée de
programmation fonctionnelle.

Cependant, comme l'ordre dans lequel les fonctions sont utilisées n'est pas très
clair pour le programmeur, la vitesse et l'efficacité des programmes écrits dans
des langages qui utilisent l’évaluation tardive peuvent être difficiles à
comprendre.

19
Un graphe acyclique orienté (Directed Acyclic Grapg or DAG) est un graphe
orienté sans cycles. Mesurés en termes de relations qu'ils peuvent représenter,
les DAG sont plus génériques que les arbres mais moins génériques que les
graphes orientés arbitraires. Les DAG sont utiles pour représenter la structure
syntaxique des expressions arithmétiques avec des sous-expressions communes
(Aho & Ullman pag 219).

20
 Les DAG sont utiles pour représenter la structure
syntaxique des expressions arithmétiques avec des
sous-expressions communes:
((a+b)*c+((a+b)+e)*(e+f))*((a+b)*c)

21
 La deuxième approche consiste à renverser les considérations
théoriques sur la stratégie de réduction et à évaluer les arguments
des fonctions avant de transmettre les valeurs à la fonction. C'est
ce qu'on appelle l'ordre applicatif ou l'évaluation hâtive.
 Ce dernier nom apparaît parce que les arguments de la fonction
sont évalués même lorsqu'ils peuvent être inutiles. Bien sûr, une
évaluation hâtive signifie que certaines expressions peuvent
boucler, entraînant un régime tardif (paresseux). Mais cela est
considéré comme acceptable, car ces cas sont généralement
faciles à éviter dans la pratique.

22
 Le fait que les fonctions ne soient pas automatiquement
évaluées est d'une importance cruciale pour les programmeurs
avancés. Il donne un contrôle précis sur l'évaluation des
expressions et peut être utilisé pour imiter de nombreux cas
utiles d'évaluation tardive / hâtive.
 Il existe également une fonction spéciale, eval, qui convertit les
expressions en valeurs.
 L'utilisation de "eval" nous permet de créer des fonctionnalités
primitives supplémentaires, avec leurs propres procédures de
réduction spéciales, plutôt que de les implémenter directement
en termes de fonctions.

23
Supposons que nous indentons pour utiliser la fonction CHECK définie
comme :
CHECK list, element
COND
{
{list = nill, false ()}
{CAR(list) = element, true()}
{true(), CHECK(CDR(list),element)}
}

dans le corps d'une nouvelle fonction ConditionalCONS qui ajoute des


éléments uniques à une liste et ignore les éléments qui se répètent:
ConditionalCONS list, element
COND
{
{eval(CHECK (list,element)), list}
{true(), CONS(list,element)}
}

Dans l’exemple, nous devons évaluer explicitement la fonction CHECK pour


convertir l'expression en une valeur booléenne comme demandé par la
construction COND. En fait, nous modifions la stratégie de réduction habituelle de
sorte que l'expression booléenne soit d'abord évaluée, et ensuite seulement
exactement l'un des deux bras est évalué, selon le cas. Sinon, le système
essaierait de faire une réduction de CONS (liste, élément) même si ce n'est pas
nécessaire.

24
 dans un certain sens opposé à la fonction eval
 renvoie explicitement un paramètre sous forme d'expression, c'est-à-dire "Quote" indique à
l'interpréteur de ne pas évaluer l'argument qu'il contient
 Notez que la fonction ConditionalCONS (list, element) renvoie une liste d'objets qui ne
peuvent généralement pas être évalués
ConditionalCONS([‘Nick’,’Alex’],’Nick’) = [‘Nick’,’Alex’]
ConditionalCONS([‘Nick’,’Alex’],’Palina’) = [‘Palina’,‘Nick’,’Alex’]

 Supposons que nous ayons également défini la fonction getNumberOfListElements


comme:

getNumberOfListElements list
COND
{
{list = nill, 0}
{true(), 1 + getNumberOfListElements (CDR(list))}
}

25
 En cas d'évaluation hâtive, la combinaison de ces deux
fonctions doit être définie à l'aide de la fonction « quote »:
GetNumberOfListElements (quote(ConditionalCONS(list,element)));

 En cas d'évaluation tardive, la fonction:


GetNumberOfListElements (ConditionalCONS(([‘Nick’,’Alex’],’Palina’))
renvoie la forme normale [+,1,[+,1,1]]
 la fonction:
eval(GetNumberOfListElements (ConditionalCONS(([‘Nick’,’Alex’],’Palina’)))
renvoie juste la valeur “3”

26
 Le calcul lambda est un modèle universel de calcul, c'est-à-dire
que tout calcul pouvant être exprimé a l’aide d’une machine
Turing, peut également être exprimé dans le calcul Lambda
 Le paradigme de programmation fonctionnelle est basé sur le
calcul Lambda
 De plus, on dit que le Paradigme Fonctionnel de Programmation
n'est qu'une implémentation du Calcul Lambda
 En savoir plus sur le calcul Lambda :
http://www.inf.fu-berlin.de/lehre/WS03/alpi/lambda.pdf

Le calcul lambda peut être appelé le plus petit langage de programmation universel au
monde. Le calcul lambda se compose d'une seule règle de transformation (substitution de
variable) et d'un seul schéma de définition de fonction. Il a été introduit dans les années
1930 par Alonzo Church comme un moyen de formaliser le concept de calculabilité
effective. Le calcul lambda est universel en ce sens que toute fonction calculable peut être
exprimée et évaluée à l'aide de ce formalisme. Elle est donc équivalente aux machines de
Turing. Cependant, le calcul met l'accent sur l'utilisation des règles de transformation et ne
se soucie pas de la machine réelle qui les implémente. C'est une approche plus liée au
logiciel qu'au matériel.
Le calcul lambda contribue a la résolution de problèmes par les mathématiques et la
récursivité avec applicabilité théorique aux compilateurs at au algorithmes de résolution
pour la modélisation informatique de problèmes.
Une machine de Turing est une sorte de machine à états. A tout moment, la machine se
trouve dans l'un quelconque d'un nombre fini d'états. Les instructions pour une machine
de Turing consistent en des conditions spécifiées dans lesquelles la machine passera d'un
état à un autre.
http://plato.stanford.edu/entries/turing-machine/#Definition

27
 Une fonction accepte une entrée et produit une sortie. Supposons
que nous ayons une fonction "goodGuy (x)" qui produit le résultat
suivant : "x" is a good guy
 Évidemment, la fonction goodGuy produit les sorties suivantes
pour les entrées correspondantes:
Nick -> Nick is a good guy
Alex -> Alex is a good guy
 Nous pouvons utiliser le calcul Lambda pour décrire une telle
fonction comme :
Lx.goodGuy x
 C'est ce qu'on appelle une expression Lambda ou une abstraction
(ici, le "L" est censé être un caractère grec "lambda" minuscule - λ)

28
 Si nous voulons appliquer la fonction à un argument, nous
utilisons la syntaxe suivante: (Lx.goodGuy x)Alex
 Cette notation est connue sous le nom d'application d'une
abstraction (ou simplement application)
 L’application (Lx.goodGuy x)Alex rends ‘Alex is a good guy’
 Les fonctions peuvent également être le résultat de l'application
d'une expression lambda, comme avec cette fonction Guy (x, y)
qui produit le résultat suivant: “x” is a “y” guy
Ly.Lx.Guy x y
 Nous pouvons utiliser l'application de cette abstraction pour créer
une abstraction goodGuy:
(Ly.Lx.Guy x y)good -> Lx.goodGuy x

Nous pouvons alimenter la fonction getNameByPhone avec la fonction "goodGuy":


(Lx.goodGuy x) (Lp.getNameByPhone p)5831618

29
 Les expressions lambda sont définies de manière récursive avec la
syntaxe suivante:
lambda-expression ::= variable
| constant
| application
| abstraction
application ::= (lambda-expression)lambda-expression
abstraction ::= Lvariable.lambda-expression
 Un "nom", aussi appelé "variable", est un identificateur
 Une expression peut être encadrée de parenthèses pour plus de
clarté, c'est-à-dire que si E est une expression, (E) est de même
 Les seuls mots clés utilisés dans le langage sont L (Lambda) et « . »

30
 L'évaluation résulte de l'application de deux règles de réduction:
◦ La règle de réduction alpha indique que nous pouvons renommer de manière
cohérente les liaisons de variables: Lx.E -> Lz.{z/x}E
 où {z / x} E signifie la substitution de z à x pour toute occurrence de x dans E.
◦ La règle de réduction bêta dit que l'application d'une expression lambda à un
argument est le remplacement cohérent de l'argument de la variable liée de
l'expression lambda dans son corps : (Lx.P) Q -> [Q / x] P
 où [Q/x] P signifie substitution de Q pour x pour toute occurrence libre de x dans P.
 Le théorème de Church-Rosser stipule que le résultat final d'une
chaîne de substitutions ne dépend pas de l'ordre dans lequel les
substitutions sont effectuées

Le théorème de Church – Rosser stipule que s'il existe deux réductions


distinctes à partir du même terme de calcul lambda, alors il existe un terme
accessible à partir de chaque réduction via une séquence (éventuellement vide)
de réductions.

Considérant le calcul lambda comme un système de réécriture abstrait, le


théorème de Church – Rosser est un théorème de confluence. En conséquence
du théorème, un terme du calcul lambda a au plus une forme normale, justifiant
la référence à «la forme normale» d'un certain terme. Le théorème a été prouvé
en 1936 par Alonzo Church et J. Barkley Rosser.

31
 Comme il a déjà été mentionné, le calcul Lambda est un modèle universel de calcul. Pour le
montrer, voici la traduction d'une structure de contrôle conditionnel en calcul lambda:
 Nous pouvons définir trois abstractions lambda comme suit :
true = Lx.Ly.x
false = Lx.Ly.y
if-then-else = La.Lb.Lc.((a)b)c
 Considérez l'application Lambda suivante
(((if-then-else)false)Nick)Palina
meaning something like
if (false)
then Nick
else Palina
 De toute évidence, nous nous attendrions à un résultat de réduction en tant que "Palina".

(Lx.x)y = [y/x]x = y
Dans cette transformation, la notation [y / x] est utilisée pour indiquer que toutes
les occurrences de x sont remplacées par y dans l'expression à droite.

32
(((if-then-else)false)Nick)Palina =
(((La.Lb.Lc.((a)b)c)Lx.Ly.y)Nick)Palina = /*a -> false -> Lx.Ly.y */
((Lb.Lc.((Lx.Ly.y)b)c)Nick)Palina = /*b -> ‘Nick’ */
(Lc.((Lx.Ly.y)Nick)c)Palina = /*c -> ‘Palina’ */
((Lx.Ly.y)Nick)Palina = /*x -> ‘Nick’ */
(Ly.y) Palina = /*y -> ‘Palina’ */
Palina

 Vous ne voudriez pas vraiment programmer comme ça, cependant!

33
 Bien que le calcul lambda soit suffisamment puissant pour exprimer
n'importe quel programme, cela ne signifie pas que vous voudriez
réellement le faire. Après tout, la machine de Turing offre une base de
calcul tout aussi puissante.
 La force du calcul lambda est qu'il est facilement utilisé comme une "colle"
au-dessus d'un monde plus riche de primitives.
 Ses avantages en tant que «colle» sont qu'il a une correspondance
naturelle avec la façon dont les gens programment, et les techniques de
compilation naturelles produisent un code haute performance.
 Ce dernier passe par des optimisations dites "appel de queue" et "passage
de continuation" ("tail-call" et "continuation-passing" ), qui pourraient faire
l'objet de futures discussions.

34
1. Les expressions lambda peuvent être comprises localement - leur
dépendance vis-à-vis de leur environnement passe entièrement par
leurs variables libres.
2. Les expressions lambda ont tendance à avoir moins de variables
libres et plus de variables liées que le code impératif comparable, car
elles ne reposent pas autant sur l’attribution pour exprimer le calcul.
3. Un programme impératif procède en modifiant certaines valeurs
globalement accessibles stockées dans une certaine zone de la
mémoire. Au contraire, un programme fonctionnel procède par
application de fonction et retour de valeurs. Cela élimine de grandes
classes d'erreurs associées au maintien d’un repositoire global de
valeurs.

Variable libre et variable liée


En calcul, tous les noms sont locaux aux définitions. Dans la fonction Lambda
x.x, nous disons que x est "lié" puisque son occurrence dans le corps de la
définition est précédée de x.
Un nom non précédé d'un "." est appelée une « variable libre ». Dans
l'expression
(Lambda x.xy) la variable x est liée et y est libre. Dans l'expression
(Lambda x.x) (Lambda y.yx) le x dans le corps de la première expression à partir
de la gauche est lié au premier. Le y dans le corps de la seconde expression est
lié à la seconde et le x est libre. Il est très important de noter que le x dans la
deuxième expression est totalement indépendant du x dans la première
expression.

35

Vous aimerez peut-être aussi