Fungsi (ECMAScript)
Yang Anda butuhkan hanyalah definisi fungsi dan panggilan fungsi. Anda tidak memerlukan fungsi percabangan, kondisional, operator atau builtin apa pun. Saya akan mendemonstrasikan implementasi menggunakan ECMAScript.
Pertama, mari kita mendefinisikan dua fungsi yang disebut true
dan false
. Kita dapat mendefinisikannya dengan cara apa pun yang kita inginkan, mereka sepenuhnya arbitrer, tetapi kita akan mendefinisikannya dengan cara yang sangat istimewa yang memiliki beberapa kelebihan seperti yang akan kita lihat nanti:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
tru
adalah fungsi dengan dua parameter yang mengabaikan argumen kedua dan mengembalikan yang pertama. fls
juga merupakan fungsi dengan dua parameter yang mengabaikan argumen pertama dan mengembalikan yang kedua.
Mengapa kami menyandikan tru
dan fls
dengan cara ini? Nah, dengan cara ini, kedua fungsi tidak hanya mewakili dua konsep true
dan false
, tidak, pada saat yang sama, mereka juga mewakili konsep "pilihan", dengan kata lain, mereka juga merupakan if
/ then
/ else
ekspresi! Kami mengevaluasi if
kondisi dan meneruskannya then
blok dan else
blok sebagai argumen. Jika kondisi dievaluasi tru
, maka akan mengembalikan then
blok, jika mengevaluasi fls
, akan mengembalikan else
blok. Ini sebuah contoh:
tru(23, 42);
// => 23
Ini kembali 23
, dan ini:
fls(23, 42);
// => 42
kembali 42
, seperti yang Anda harapkan.
Namun, ada kerutan:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
Ini mencetak keduanya then branch
dan else branch
! Mengapa?
Yah, itu mengembalikan nilai pengembalian argumen pertama, tetapi mengevaluasi kedua argumen, karena ECMAScript ketat dan selalu mengevaluasi semua argumen ke suatu fungsi sebelum memanggil fungsi. TKI: ia mengevaluasi argumen pertama console.log("then branch")
, yang hanya mengembalikan undefined
dan memiliki efek samping dari pencetakan then branch
ke konsol, dan mengevaluasi argumen kedua, yang juga mengembalikan undefined
dan mencetak ke konsol sebagai efek samping. Kemudian, ia mengembalikan yang pertama undefined
.
Di λ-calculus, tempat penyandian ini ditemukan, itu bukan masalah: λ-calculus murni , yang berarti tidak memiliki efek samping; karena itu Anda tidak akan pernah memperhatikan bahwa argumen kedua juga akan dievaluasi. Plus, λ-calculus malas (atau setidaknya, sering dievaluasi dalam urutan normal), artinya, itu tidak benar-benar mengevaluasi argumen yang tidak diperlukan. Jadi, TKI: dalam λ-calculus argumen kedua tidak akan pernah dievaluasi, dan jika ya, kami tidak akan melihat.
Namun, ECMAScript ketat , yaitu selalu mengevaluasi semua argumen. Well, sebenarnya, tidak selalu: yang if
/ then
/ else
, misalnya, hanya mengevaluasi then
cabang jika kondisi true
dan hanya mengevaluasi else
cabang jika kondisifalse
. Dan kami ingin mereplikasi perilaku ini dengan kami iff
. Untungnya, meskipun ECMAScript tidak malas, ia memiliki cara untuk menunda evaluasi sepotong kode, dengan cara yang sama hampir setiap bahasa lainnya: membungkusnya dalam suatu fungsi, dan jika Anda tidak pernah memanggil fungsi itu, kode tersebut akan tidak pernah dieksekusi.
Jadi, kami membungkus kedua blok dalam suatu fungsi, dan pada akhirnya memanggil fungsi yang dikembalikan:
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
cetakan then branch
dan
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
cetakan else branch
.
Kita bisa menerapkan cara tradisional if
/ then
/ else
ini:
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
Sekali lagi, kita memerlukan beberapa pembungkus fungsi tambahan saat memanggil iff
fungsi dan kurung fungsi panggilan ekstra dalam definisi iff
, untuk alasan yang sama seperti di atas:
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
Sekarang kita memiliki dua definisi tersebut, kita dapat menerapkannya or
. Pertama, kita melihat tabel kebenaran or
: jika operan pertama adalah benar, maka hasil dari ekspresi sama dengan operan pertama. Kalau tidak, hasil dari ekspresi adalah hasil dari operan kedua. Singkatnya: jika operan pertama adalah true
, kami mengembalikan operan pertama, jika tidak, kami mengembalikan operan kedua:
const orr = (a, b) => iff(a, () => a, () => b);
Mari kita periksa apakah itu berfungsi:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
Besar! Namun, definisi itu terlihat agak jelek. Ingat, tru
dan fls
sudah bertindak seperti bersyarat sendiri, jadi benar-benar tidak perlu iff
, dan dengan demikian semua fungsi itu membungkus sama sekali:
const orr = (a, b) => a(a, b);
Itu dia: or
(ditambah operator boolean lainnya) yang didefinisikan dengan definisi fungsi dan panggilan fungsi hanya dalam beberapa baris:
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)();
Sayangnya, implementasi ini agak tidak berguna: tidak ada fungsi atau operator dalam ECMAScript yang mengembalikan tru
atau fls
, mereka semua kembali true
atau false
, jadi kami tidak dapat menggunakannya dengan fungsi kami. Tapi masih banyak yang bisa kita lakukan. Misalnya, ini adalah implementasi dari daftar yang terhubung sendiri:
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
Objek (Scala)
Anda mungkin telah memperhatikan sesuatu yang aneh: tru
dan fls
memainkan peran ganda, mereka bertindak baik sebagai nilai data true
dan false
, tetapi pada saat yang sama, mereka juga bertindak sebagai ekspresi kondisional. Mereka adalah data dan perilaku , yang digabungkan menjadi satu ... uhm ... "benda" ... atau (berani saya katakan) objek !
Memang, tru
dan fls
benda. Dan, jika Anda pernah menggunakan Smalltalk, Self, Newspeak atau bahasa berorientasi objek lainnya, Anda akan memperhatikan bahwa mereka mengimplementasikan boolean dengan cara yang persis sama. Saya akan menunjukkan implementasi seperti ini di sini di 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
BTW ini adalah alasan Ganti Kondisional Dengan Polimorfisme Refactoring selalu berfungsi: Anda selalu dapat mengganti setiap dan setiap kondisional dalam program Anda dengan pengiriman pesan polimorfik, karena seperti yang baru saja kami tunjukkan, pengiriman pesan polimorfik dapat menggantikan kondisional dengan hanya mengimplementasikannya. Bahasa seperti Smalltalk, Self dan Newspeak adalah bukti keberadaan itu, karena bahasa-bahasa itu bahkan tidak memiliki persyaratan. (Mereka juga tidak memiliki loop, BTW, atau benar - benar segala jenis struktur kontrol bawaan bahasa kecuali untuk pengiriman pesan polimorfik alias panggilan metode virtual.)
Pencocokan Pola (Haskell)
Anda juga bisa mendefinisikan or
menggunakan pencocokan pola, atau sesuatu seperti definisi fungsi parsial Haskell:
True ||| _ = True
_ ||| b = b
Tentu saja, pencocokan pola adalah bentuk eksekusi bersyarat, tetapi sekali lagi, begitu juga pengiriman pesan berorientasi objek.