Funktionen (ECMAScript)
Sie benötigen lediglich Funktionsdefinitionen und Funktionsaufrufe. Sie benötigen keine Verzweigungen, Bedingungen, Operatoren oder eingebauten Funktionen. Ich werde eine Implementierung mit ECMAScript demonstrieren.
Definieren wir zunächst zwei Funktionen namens trueund false. Wir können sie so definieren, wie wir wollen, sie sind völlig willkürlich, aber wir werden sie auf eine ganz besondere Art definieren, die einige Vorteile hat, wie wir später sehen werden:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
truist eine Funktion mit zwei Parametern, die das zweite Argument einfach ignoriert und das erste zurückgibt. flsist auch eine Funktion mit zwei Parametern, die das erste Argument einfach ignoriert und das zweite zurückgibt.
Warum haben wir truund auf flsdiese Weise codiert ? Nun, auf diese Weise, die beiden Funktionen nicht nur repräsentieren die beiden Begriffe trueund false, nein, zugleich, sie auch das Konzept der „Wahl“, mit anderen Worten darstellen, sind sie auch ein if/ then/ elseAusdruck! Wir werten die ifBedingung aus und übergeben sie als Argumente an den thenBlock und den elseBlock. Wenn die Bedingung den Wert "0" ergibt tru, wird der thenBlock zurückgegeben. Wenn die Bedingung den Wert "0" ergibt fls, wird der elseBlock zurückgegeben. Hier ist ein Beispiel:
tru(23, 42);
// => 23
Dies kehrt zurück 23und dies:
fls(23, 42);
// => 42
kehrt zurück 42, so wie Sie es erwarten würden.
Es gibt jedoch eine Falte:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
Dies druckt sowohl then branch als auch else branch! Warum?
Nun, es gibt den Rückgabewert des ersten Arguments zurück, wertet jedoch beide Argumente aus, da ECMAScript streng ist und immer alle Argumente für eine Funktion auswertet, bevor die Funktion aufgerufen wird. IOW: Es wertet das erste Argument aus console.log("then branch"), das einfach zurückgibt undefinedund den Nebeneffekt des Druckens then branchauf die Konsole hat, und es wertet das zweite Argument aus, das ebenfalls undefinedals Nebeneffekt zurückgibt und auf die Konsole druckt. Dann wird der erste zurückgegeben undefined.
In λ-Kalkül, wo diese Kodierung erfunden wurde, ist das kein Problem: λ-Kalkül ist rein , was bedeutet, dass es keine Nebenwirkungen hat; daher würde man nie bemerken, dass das zweite Argument auch ausgewertet wird. Außerdem ist λ-Kalkül faul (oder zumindest wird es oft in normaler Reihenfolge ausgewertet), was bedeutet, dass es keine Argumente auswertet, die nicht benötigt werden. Also, IOW: In der λ-Rechnung würde das zweite Argument niemals ausgewertet, und wenn es so wäre, würden wir es nicht bemerken.
ECMAScript ist jedoch streng , dh es wertet immer alle Argumente aus. Nun, eigentlich nicht immer: der if/ then/ elsezum Beispiel wertet nur den thenZweig , wenn die Bedingung ist trueund wertet nur den elseZweig , wenn die Bedingung ist false. Und dieses Verhalten wollen wir mit unserem replizieren iff. Glücklicherweise kann ECMAScript, obwohl es nicht faul ist, die Auswertung eines Codeteils verzögern, so wie es fast jede andere Sprache tut: Es wird in eine Funktion eingebunden, und wenn Sie diese Funktion nie aufrufen, wird der Code dies tun nie hingerichtet werden.
Wir binden also beide Blöcke in eine Funktion ein und rufen am Ende die zurückgegebene Funktion auf:
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
druckt then branchund
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
druckt else branch.
Wir konnten die traditionelle implementieren if/ then/ auf elsediese Weise:
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
Auch hier benötigen wir einen zusätzlichen Funktionsumbruch beim Aufrufen der iffFunktion und der zusätzlichen Funktionsaufrufklammern in der Definition von iff, und zwar aus dem gleichen Grund wie oben:
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
Nachdem wir diese beiden Definitionen haben, können wir sie implementieren or. Zunächst untersuchen wir die Wahrheitstabelle nach or: Wenn der erste Operand wahr ist, ist das Ergebnis des Ausdrucks dasselbe wie der erste Operand. Ansonsten ist das Ergebnis des Ausdrucks das Ergebnis des zweiten Operanden. Kurz gesagt: Wenn der erste Operand ist true, geben wir den ersten Operanden zurück, andernfalls geben wir den zweiten Operanden zurück:
const orr = (a, b) => iff(a, () => a, () => b);
Lassen Sie uns herausfinden, dass es funktioniert:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
Groß! Diese Definition sieht jedoch etwas hässlich aus. Denken Sie daran, truund flshandeln Sie bereits wie eine Bedingung für sich, so dass wirklich keine Notwendigkeit besteht iff, und daher all diese Funktionen überhaupt zu umschließen:
const orr = (a, b) => a(a, b);
Da haben Sie es: or(plus andere boolesche Operatoren) definiert mit nichts als Funktionsdefinitionen und Funktionsaufrufen in nur wenigen Zeilen:
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)();
Leider ist diese Implementierung ziemlich nutzlos: Es gibt keine Funktionen oder Operatoren in ECMAScript, die zurückgeben truoder flsalle zurückgeben trueoder false, daher können wir sie nicht mit unseren Funktionen verwenden. Aber wir können noch viel tun. Dies ist beispielsweise eine Implementierung einer einfach verknüpften Liste:
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
Gegenstände (Scala)
Sie haben vielleicht bemerkt etwas Besonderes: truund flseine doppelte Rolle spielen, wirken sie sowohl als die Datenwerte trueund false, aber zur gleichen Zeit, sie auch als bedingter Ausdruck handeln. Sie sind Daten und Verhalten , gebündelt in einem ... ähm ... "Ding" ... oder (wage ich zu sagen) Objekt !
In der Tat truund flssind Objekte. Und wenn Sie jemals Smalltalk, Self, Newspeak oder andere objektorientierte Sprachen verwendet haben, werden Sie feststellen, dass sie Boolesche Werte genauso implementieren. Ich werde eine solche Implementierung hier in Scala demonstrieren:
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
Dies ist übrigens der Grund, warum das Refactoring "Bedingt durch Polymorphismus ersetzen" immer funktioniert: Sie können immer alle Bedingungen in Ihrem Programm durch polymorphen Nachrichtenversand ersetzen, da der polymorphe Nachrichtenversand, wie wir gerade gezeigt haben, Bedingungen durch einfaches Implementieren ersetzen kann. Sprachen wie Smalltalk, Self und Newspeak sind der Existenzbeweis dafür, weil diese Sprachen nicht einmal Bedingungen haben. (Sie haben auch keine Schleifen, BTW oder wirklich irgendeine Art von in die Sprache eingebauten Kontrollstrukturen, außer für den polymorphen Nachrichtenversand, auch bekannt als virtuelle Methodenaufrufe.)
Mustervergleich (Haskell)
Sie können auch ormithilfe des Mustervergleichs oder so etwas wie den Definitionen der Teilfunktionen von Haskell definieren:
True ||| _ = True
_ ||| b = b
Natürlich ist der Mustervergleich eine Form der bedingten Ausführung, aber auch der objektorientierte Nachrichtenversand.