函数(ECMAScript)
您只需要函数定义和函数调用。您不需要任何分支,条件,运算符或内置函数。我将演示使用ECMAScript的实现。
首先,让我们定义两个名为true
和的函数false
。我们可以根据需要定义它们,它们是完全任意的,但是我们将以非常特殊的方式定义它们,这具有一些优势,我们将在后面看到:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
tru
是具有两个参数的函数,它仅忽略其第二个参数并返回第一个参数。fls
也是一个具有两个参数的函数,它仅忽略其第一个参数并返回第二个参数。
为什么我们编码tru
和fls
这样?那么,这样一来,这两个功能不仅代表两个概念true
,并false
没有在同一时间,他们也代表了“选择”的概念,换句话说,他们也是一个if
/ then
/ else
表情!我们评估if
条件并将其传递给then
块和else
块作为参数。如果条件的计算结果为tru
,它将返回该then
块;如果该条件的计算结果为fls
,它将返回该else
块。这是一个例子:
tru(23, 42);
// => 23
返回23
,这是:
fls(23, 42);
// => 42
42
就像您期望的那样返回。
但是有皱纹:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
这将打印两个 then branch
和else branch
!为什么?
好吧,它返回第一个参数的返回值,但是它会评估两个参数,因为ECMAScript是严格的,并且始终在调用函数之前评估函数的所有参数。IOW:它评估第一个参数console.log("then branch")
,它只是返回undefined
并具有打印then branch
到控制台的副作用,并且它评估第二个参数,它也返回undefined
并打印到控制台作为副作用。然后,它返回first undefined
。
在发明了这种编码的λ微积分中,这不是问题:λ微积分是pure,这意味着它没有任何副作用。因此,您永远不会注意到第二个参数也会被求值。另外,λ演算是惰性的(或者至少经常是按正常顺序求值的),这意味着,它实际上并不求值不需要的参数。因此,IOW:在λ微积分中,永远不会评估第二个参数,如果是,我们将不会注意到。
但是,ECMAScript是strict,即,它始终对所有参数求值。好吧,实际上,并非总是如此:例如,if
/ then
/ else
仅then
在条件为true
时才评估else
分支,而在条件为时才评估分支false
。而且我们想用来复制这种行为iff
。值得庆幸的是,即使ECMAScript并不懒惰,它也可以延迟对一段代码的求值,几乎所有其他语言都可以这样做:将其包装在函数中,并且如果您从不调用该函数,代码将永远不会被执行。
因此,我们将两个块都包装在一个函数中,最后调用返回的函数:
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
印刷品then branch
和
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
版画else branch
。
我们可以实现传统if
/ then
/ else
是这样的:
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
同样,由于上述原因,我们在调用函数时需要一些额外的函数包装,iff
并且在的定义中需要额外的函数调用括号iff
:
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
现在我们有了这两个定义,我们可以实现了or
。首先,我们看一下真值表or
:如果第一个操作数是真实的,则表达式的结果与第一个操作数相同。否则,表达式的结果就是第二个操作数的结果。简而言之:如果第一个操作数为true
,则返回第一个操作数,否则返回第二个操作数:
const orr = (a, b) => iff(a, () => a, () => b);
让我们检查一下它是否有效:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
大!但是,该定义看起来很难看。请记住,tru
并且fls
它们本身就已经像一个有条件的行为一样,因此确实不需要iff
,因此所有这些函数都可以包装:
const orr = (a, b) => a(a, b);
有了它:(or
加上其他布尔运算符)仅用几行定义,只定义了函数定义和函数调用:
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)();
不幸的是,这种实现是相当无用的:ECMAScript中没有返回tru
或的函数或运算符fls
,它们都返回true
或false
,因此我们不能在函数中使用它们。但是,我们还有很多事情可以做。例如,这是单链接列表的实现:
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
对象(标量)
你可能已经注意到一些奇怪:tru
和fls
扮演着双重角色,他们既充当数据值true
和false
,但与此同时,他们也作为一个条件表达式。它们是数据和行为,捆绑成一个……呃……“事物”……或(我敢说)对象!
事实上,tru
和fls
都是对象。而且,如果您曾经使用过Smalltalk,Self,Newspeak或其他面向对象的语言,您会注意到它们以完全相同的方式实现布尔值。我将在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
这就是为什么使用多态重构替换条件始终有效的原因:您总是可以用多态消息分发来替换程序中的任何条件,因为正如我们刚刚所示,多态消息分发可以通过简单地实现而代替条件。Smalltalk,Self和Newspeak之类的语言就是存在的证明,因为这些语言甚至没有条件。(除了多态消息分发又称为虚拟方法调用,它们也没有循环,BTW或实际上没有任何类型的语言内置控制结构。)
模式匹配(Haskell)
您还可以or
使用模式匹配或类似Haskell的部分函数定义的方式进行定义:
True ||| _ = True
_ ||| b = b
当然,模式匹配是条件执行的一种形式,但是面向对象的消息分发也是如此。