Fonctions (ECMAScript)
Vous n'avez besoin que de définitions de fonctions et d'appels de fonctions. Vous n'avez pas besoin de branches, de conditions, d'opérateurs ou de fonctions intégrées. Je vais démontrer une implémentation en utilisant ECMAScript.
Premièrement, définissons deux fonctions appelées true
et false
. Nous pouvons les définir comme bon nous semble, ils sont complètement arbitraires, mais nous les définirons de manière très spéciale, ce qui présente certains avantages, comme nous le verrons plus tard:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
tru
est une fonction à deux paramètres qui ignore simplement son deuxième argument et renvoie le premier. fls
est également une fonction à deux paramètres qui ignore simplement son premier argument et renvoie le second.
Pourquoi avons-nous encodé tru
et de fls
cette façon? Eh bien, de cette façon, les deux fonctions ne représentent pas seulement les deux concepts de true
et false
, en même temps, elles représentent également le concept de "choix", en d’autres termes, elles sont aussi une expression if
/ then
/ else
! Nous évaluons la if
condition et lui passons le then
bloc et le else
bloc comme arguments. Si la condition est évaluée à tru
, elle retournera le then
bloc. Si elle évaluera à fls
, elle retournera le else
bloc. Voici un exemple:
tru(23, 42);
// => 23
Ceci retourne 23
et ceci:
fls(23, 42);
// => 42
retourne 42
, comme on peut s'y attendre.
Il y a une ride, cependant:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
Cela imprime à la fois then branch
et else branch
! Pourquoi?
Eh bien, il renvoie la valeur de retour du premier argument, mais il évalue les deux arguments, car ECMAScript est strict et évalue toujours tous les arguments d'une fonction avant d'appeler la fonction. IOW: il évalue le premier argument qui est console.log("then branch")
, qui retourne simplement undefined
et a l’effet secondaire d’imprimer then branch
sur la console, et le deuxième argument, qui retourne également undefined
et est imprimé sur la console en tant qu’effet secondaire. Ensuite, il retourne le premier undefined
.
Dans le λ-calcul, où ce codage a été inventé, ce n'est pas un problème: le λ-calcul est pur , ce qui signifie qu'il n'a pas d'effets secondaires; par conséquent, vous ne remarquerez jamais que le deuxième argument est également évalué. De plus, le λ-calcul est paresseux (ou du moins, il est souvent évalué dans un ordre normal), ce qui signifie qu'il n'évalue pas les arguments inutiles. Donc, IOW: dans le λ-calcul, le deuxième argument ne serait jamais évalué, et si c'était le cas, nous ne le remarquerions pas.
ECMAScript, cependant, est strict , c’est-à-dire qu’il évalue toujours tous les arguments. En fait, pas toujours: le if
/ then
/ else
, par exemple, n’évalue la then
branche que si la condition est true
et n’évalue que la else
branche si la condition est false
. Et nous voulons reproduire ce comportement avec notre iff
. Heureusement, même si ECMAScript n’est pas paresseux, il a le moyen de retarder l’évaluation d’un morceau de code, comme presque toutes les autres langues: encapsulez-le dans une fonction et si vous n’appelez jamais cette fonction, le code ne jamais être exécuté.
Donc, nous encapsulons les deux blocs dans une fonction et, à la fin, appelons la fonction renvoyée:
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
estampes then branch
et
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
impressions else branch
.
Nous pourrions mettre en œuvre le traditionnel if
/ then
/ de else
cette façon:
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
Encore une fois, nous avons besoin de fonctions supplémentaires lors de l'appel de la iff
fonction et de l'appel de fonction supplémentaire entre parenthèses dans la définition de iff
, pour la même raison que ci-dessus:
const iff = (cnd, thn, els) => cnd(thn, els)();
iff(tru, () => console.log("then branch"), () => console.log("else branch"));
// then branch
iff(fls, () => console.log("then branch"), () => console.log("else branch"));
// else branch
Maintenant que nous avons ces deux définitions, nous pouvons les appliquer or
. Premièrement, nous examinons la table de vérité pour or
: si le premier opérande est la vérité, le résultat de l'expression est identique à celui du premier opérande. Sinon, le résultat de l'expression est le résultat du deuxième opérande. En bref: si le premier opérande est true
, nous retournons le premier opérande, sinon nous retournons le deuxième opérande:
const orr = (a, b) => iff(a, () => a, () => b);
Voyons si cela fonctionne:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
Génial! Cependant, cette définition est un peu moche. Rappelez-vous, tru
et fls
agissez déjà comme un conditionnel tout seul, donc il n'y a vraiment pas besoin de iff
, et donc de toute cette fonction encapsulant du tout:
const orr = (a, b) => a(a, b);
Voilà ce que vous avez: or
(plus d’autres opérateurs booléens) défini avec seulement des définitions de fonctions et des appels de fonctions en seulement quelques lignes:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els,
orr = (a , b ) => a(a, b),
nnd = (a , b ) => a(b, a),
ntt = a => a(fls, tru),
xor = (a , b ) => a(ntt(b), b),
iff = (cnd, thn, els) => cnd(thn, els)();
Malheureusement, cette implémentation est plutôt inutile: il n'y a pas de fonctions ou d'opérateurs dans ECMAScript qui retournent tru
ou qui reviennent fls
tous true
ou false
, nous ne pouvons donc pas les utiliser avec nos fonctions. Mais il y a encore beaucoup à faire. Par exemple, il s’agit d’une implémentation d’une liste à lien unique:
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
Objets (Scala)
Vous avez peut-être remarqué quelque chose de particulier: tru
et fls
jouant un double rôle, ils agissent à la fois comme valeurs de données true
et false
, mais en même temps, ils agissent également comme une expression conditionnelle. Ce sont des données et des comportements , regroupés dans un… euh… «chose»… ou (oserais-je dire) objet !
En effet, tru
et fls
sont des objets. Et si vous avez déjà utilisé Smalltalk, Self, Newspeak ou d’autres langages orientés objet, vous aurez remarqué qu’ils implémentent les booléens exactement de la même manière. Je démontrerai une telle implémentation ici à Scala:
sealed abstract trait Buul {
def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): T
def &&&(other: ⇒ Buul): Buul
def |||(other: ⇒ Buul): Buul
def ntt: Buul
}
case object Tru extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): U = thn
override def &&&(other: ⇒ Buul) = other
override def |||(other: ⇒ Buul): this.type = this
override def ntt = Fls
}
case object Fls extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): V = els
override def &&&(other: ⇒ Buul): this.type = this
override def |||(other: ⇒ Buul) = other
override def ntt = Tru
}
object BuulExtension {
import scala.language.implicitConversions
implicit def boolean2Buul(b: ⇒ Boolean) = if (b) Tru else Fls
}
import BuulExtension._
(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3
C’est la raison pour laquelle le refactoring Remplacer conditionnel par polymorphisme fonctionne toujours: vous pouvez toujours remplacer n’importe quelle condition de votre programme par une distribution de messages polymorphes, car, comme nous venons de le montrer, la distribution de messages polymorphes peut remplacer des conditions en les implémentant simplement. Des langages comme Smalltalk, Self et Newspeak en sont la preuve, car ils n’ont même pas de condition. (Ils ne sont pas aussi des boucles, BTW, ou vraiment une sorte de structures de contrôle intégré langue sauf pour l' expédition des messages polymorphes appels de méthode alias virtuels.)
Correspondance de modèle (Haskell)
Vous pouvez également définir en or
utilisant un filtrage par motif, ou quelque chose comme les définitions de fonctions partielles de Haskell:
True ||| _ = True
_ ||| b = b
Bien entendu, la mise en correspondance de modèles est une forme d'exécution conditionnelle, mais il en va de même pour l'envoi de messages orientés objet.