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 true
và 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;
tru
là 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. fls
cũ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 tru
và fls
theo 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 true
và 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
/ else
biểu thức! Chúng tôi đánh giá if
điều kiện và truyền cho nó then
khối và else
khối làm đối số. Nếu điều kiện ước tính tru
, nó sẽ trả về then
khối, nếu nó ước lượng fls
, nó sẽ trả về else
khố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ề undefined
và có tác dụng phụ của việc in then branch
ra bàn điều khiển, và nó đánh giá đối số thứ hai, cũng trả về undefined
và 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 then
chi nhánh nếu điều kiện là true
và chỉ đánh giá các else
chi 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 branch
và
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
/ else
theo 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 iff
hà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ớ, tru
và 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ề tru
hoặc fls
, tất cả chúng đều trả về true
hoặ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: tru
và fls
đóng một vai trò kép, chúng hoạt động cả hai như là các giá trị dữ liệu true
và 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, tru
và fls
là đố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 or
bằ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.