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 trueet 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;
truest une fonction à deux paramètres qui ignore simplement son deuxième argument et renvoie le premier. flsest également une fonction à deux paramètres qui ignore simplement son premier argument et renvoie le second.
Pourquoi avons-nous encodé truet de flscette façon? Eh bien, de cette façon, les deux fonctions ne représentent pas seulement les deux concepts de trueet 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 ifcondition et lui passons le thenbloc et le elsebloc comme arguments. Si la condition est évaluée à tru, elle retournera le thenbloc. Si elle évaluera à fls, elle retournera le elsebloc. Voici un exemple:
tru(23, 42);
// => 23
Ceci retourne 23et 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 undefinedet a l’effet secondaire d’imprimer then branchsur la console, et le deuxième argument, qui retourne également undefinedet 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 thenbranche que si la condition est trueet n’évalue que la elsebranche 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 branchet
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
impressions else branch.
Nous pourrions mettre en œuvre le traditionnel if/ then/ de elsecette 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 ifffonction 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, truet flsagissez 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 truou qui reviennent flstous trueou 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: truet flsjouant un double rôle, ils agissent à la fois comme valeurs de données trueet 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, truet flssont 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 orutilisant 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.