Hàm (ECMAScript)
Tất cả bạn cần là định nghĩa chức năng và các cuộc gọi chức năng. Bạn không cần bất kỳ chức năng phân nhánh, điều kiện, toán tử hoặc hàm dựng sẵn. Tôi sẽ chứng minh việc thực hiện bằng ECMAScript.
Đầu tiên, hãy xác định hai hàm được gọi truevà false. Chúng ta có thể định nghĩa chúng theo bất kỳ cách nào chúng ta muốn, chúng hoàn toàn tùy ý, nhưng chúng ta sẽ định nghĩa chúng theo một cách rất đặc biệt có một số lợi thế như chúng ta sẽ thấy sau:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
trulà một hàm có hai tham số đơn giản bỏ qua đối số thứ hai và trả về tham số thứ nhất. flscũng là một hàm có hai tham số đơn giản bỏ qua đối số thứ nhất và trả về tham số thứ hai.
Tại sao chúng ta mã hóa truvà flstheo cách này? Vâng, theo cách này, hai hàm không chỉ đại diện cho hai khái niệm truevà false, không, đồng thời, chúng còn đại diện cho khái niệm "sự lựa chọn", nói cách khác, chúng cũng là một if/ then/ elsebiểu thức! Chúng tôi đánh giá ifđiều kiện và truyền cho nó thenkhối và elsekhối làm đối số. Nếu điều kiện ước tính tru, nó sẽ trả về thenkhối, nếu nó ước lượng fls, nó sẽ trả về elsekhối. Đây là một ví dụ:
tru(23, 42);
// => 23
Điều này trả về 23, và điều này:
fls(23, 42);
// => 42
trả lại 42, như bạn mong đợi
Có một nếp nhăn, tuy nhiên:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
Điều này in cả then branch và else branch! Tại sao?
Chà, nó trả về giá trị trả về của đối số đầu tiên, nhưng nó đánh giá cả hai đối số, vì ECMAScript rất nghiêm ngặt và luôn đánh giá tất cả các đối số cho một hàm trước khi gọi hàm. IOW: nó đánh giá đối số đầu tiên console.log("then branch"), đơn giản là trả về undefinedvà có tác dụng phụ của việc in then branchra bàn điều khiển, và nó đánh giá đối số thứ hai, cũng trả về undefinedvà in ra bàn điều khiển như một hiệu ứng phụ. Sau đó, nó trả về đầu tiên undefined.
Trong calcul-compus, nơi mã hóa này được phát minh, đó không phải là vấn đề:-compus là thuần túy , có nghĩa là nó không có bất kỳ tác dụng phụ nào; do đó, bạn sẽ không bao giờ nhận thấy rằng đối số thứ hai cũng được đánh giá. Thêm vào đó,-compus là lười biếng (hoặc ít nhất, nó thường được đánh giá theo thứ tự thông thường), có nghĩa là, nó không thực sự đánh giá các đối số không cần thiết. Vì vậy, IOW: trong-tính toán, đối số thứ hai sẽ không bao giờ được đánh giá, và nếu có, chúng tôi sẽ không chú ý.
ECMAScript, tuy nhiên, là nghiêm ngặt , tức là nó luôn đánh giá tất cả các đối số. Vâng, trên thực tế, không phải lúc nào: sự if/ then/ else, ví dụ, chỉ đánh giá các thenchi nhánh nếu điều kiện là truevà chỉ đánh giá các elsechi nhánh nếu điều kiện là false. Và chúng tôi muốn nhân rộng hành vi này với iff. Rất may, mặc dù ECMAScript không lười biếng, nhưng có một cách để trì hoãn việc đánh giá một đoạn mã, giống như hầu hết mọi ngôn ngữ khác: bọc nó trong một hàm và nếu bạn không bao giờ gọi hàm đó, mã sẽ không bao giờ được thực hiện.
Vì vậy, chúng tôi bọc cả hai khối trong một hàm và cuối cùng gọi hàm được trả về:
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
in then branchvà
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
in else branch.
Chúng ta có thể thực hiện truyền thống if/ then/ elsetheo cách này:
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
Một lần nữa, chúng ta cần một số gói chức năng bổ sung khi gọi iffhàm và hàm bổ sung gọi dấu ngoặc đơn trong định nghĩa của iff, với cùng lý do như trên:
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
Bây giờ chúng ta có hai định nghĩa đó, chúng ta có thể thực hiện or. Đầu tiên, chúng ta nhìn vào bảng chân lý cho or: nếu toán hạng đầu tiên là trung thực, thì kết quả của biểu thức giống như toán hạng đầu tiên. Mặt khác, kết quả của biểu thức là kết quả của toán hạng thứ hai. Tóm lại: nếu toán hạng thứ nhất là true, chúng ta trả về toán hạng thứ nhất, nếu không chúng ta trả về toán hạng thứ hai:
const orr = (a, b) => iff(a, () => a, () => b);
Hãy kiểm tra xem nó hoạt động:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
Tuyệt quá! Tuy nhiên, định nghĩa đó có vẻ hơi xấu xí. Hãy nhớ, truvà flsđã hành động như một điều kiện một mình, vì vậy thực sự không cần thiết iff, và do đó tất cả các chức năng đó bao gồm tất cả:
const orr = (a, b) => a(a, b);
Ở đó bạn có nó: or(cộng với các toán tử boolean khác) được định nghĩa không có gì ngoài các định nghĩa hàm và các hàm gọi chỉ trong một số dòng:
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)();
Thật không may, việc triển khai này khá vô dụng: không có hàm hoặc toán tử nào trong ECMAScript trả về truhoặc fls, tất cả chúng đều trả về truehoặc false, vì vậy chúng ta không thể sử dụng chúng với các hàm của mình. Nhưng vẫn còn rất nhiều điều chúng ta có thể làm. Ví dụ: đây là một triển khai danh sách liên kết đơn:
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
Đối tượng (Scala)
Bạn có thể nhận thấy một cái gì đó đặc biệt: truvà flsđóng một vai trò kép, chúng hoạt động cả hai như là các giá trị dữ liệu truevà false, nhưng đồng thời, họ cũng đóng vai trò như một biểu thức điều kiện. Chúng là dữ liệu và hành vi , được kết hợp thành một đối tượng "điều" hay khác (tôi dám nói) !
Thật vậy, truvà flslà đối tượng. Và, nếu bạn đã từng sử dụng Smalltalk, Self, Drameak hoặc các ngôn ngữ hướng đối tượng khác, bạn sẽ nhận thấy rằng chúng thực hiện booleans theo cùng một cách chính xác. Tôi sẽ chứng minh một triển khai như vậy ở đây trong 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 này là lý do tại sao Công cụ tái cấu trúc đa hình thay thế luôn hoạt động: bạn luôn có thể thay thế bất kỳ và mọi điều kiện trong chương trình của bạn bằng việc gửi tin nhắn đa hình, bởi vì như chúng tôi vừa trình bày, việc gửi tin nhắn đa hình có thể thay thế điều kiện bằng cách đơn giản thực hiện chúng. Các ngôn ngữ như Smalltalk, Self và Drameak là bằng chứng tồn tại cho điều đó, bởi vì những ngôn ngữ đó thậm chí không có điều kiện. (Họ cũng không có các vòng lặp, BTW hoặc thực sự là bất kỳ loại cấu trúc điều khiển tích hợp ngôn ngữ nào ngoại trừ việc gửi tin nhắn đa hình hay còn gọi là phương thức ảo.)
Kết hợp mẫu (Haskell)
Bạn cũng có thể xác định orbằng cách sử dụng khớp mẫu hoặc một cái gì đó như định nghĩa hàm một phần của Haskell:
True ||| _ = True
_ ||| b = b
Tất nhiên, khớp mẫu là một hình thức thực hiện có điều kiện, nhưng sau đó, một lần nữa, việc gửi thông điệp hướng đối tượng cũng vậy.