Excursion:真正面向对象的布尔值
Scala 和 Java 都是面向对象的语言。在面向对象的语言中,您永远需要ifs 来处理任何事情!您可以总是用多态消息调度替换条件。 (如果您不熟悉 Smalltalk 术语,请将“消息”替换为“虚拟方法”。)
消息发送有什么作用?好吧,基本上,它说“if 对象有这个类,then 执行这个方法,if 它有那个类,then 执行那个其他方法”,等等。你有看到?消息发送已经是有条件的! Smalltalk 语言甚至没有条件(它也没有循环),而是使用多态消息分发。
基本思想是这样的:你有一个用于布尔值的协议 /interface /abstract class,它有一个名为 ifThenElse 的方法,它采用两个代码块(lambdas、函数)作为参数。你有两个具体的实现子类,一个用于实现ifThenElse 的“真”值,这样它就执行它的第一个参数并忽略第二个参数,另一个用于“假”值的具体实现子类,ifThenElse 执行第二个参数并忽略第一个。
如果你仔细想想,这实际上只是将Replace Conditional With Polymorphism Refactoring 带到了逻辑极端并应用于if/then/else 本身。
它看起来有点像这样(人为的)Scala 示例:
sealed abstract trait Buul {
def apply[T, U <: T, V <: T](thn: => U)(els: => V): T
}
case object Tru extends Buul {
override def apply[T, U <: T, V <: T](thn: => U)(els: => V): U = thn
}
case object Fls extends Buul {
override def apply[T, U <: T, V <: T](thn: => U)(els: => V): V = els
}
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
在 Java 中看起来像这样:
import static java.lang.System.out;
public class Test {
public static void main(String... args) {
Buul.from(2 < 3).ifThenElse(
() -> { out.println("2 is less than 3"); },
() -> { out.println("2 is greater than 3"); }
);
// 2 is less than 3
}
}
interface Buul {
void ifThenElse(CodeBlock thn, CodeBlock els);
static Buul from(boolean b) { return b ? new Tru() : new Fls(); };
class Tru implements Buul {
@Override public void ifThenElse(CodeBlock thn, CodeBlock els) { thn.execute(); };
}
class Fls implements Buul {
@Override public void ifThenElse(CodeBlock thn, CodeBlock els) { els.execute(); };
}
}
// Somehow, this is missing from java.util.functions
@FunctionalInterface interface CodeBlock {
void execute();
}
and 和or 可以用类似的方式实现。让我们暂时忽略短路。 true && something 是什么?好吧,它只取决于something,不是吗?如果something 为假,则结果为假,如果something 为真,则结果为真,或者换句话说:结果始终为something。所以,我们可以在我们的“真正”子类中实现and,就像Boolean and(Boolean other) { return other; }一样。
这同样适用于false || something。
同样,对于false && something 和true || something,结果始终是第一个操作数,或者换句话说:return this;。
现在,为了实现短路,我们所要做的就是将参数包装在一个函数中,而不是返回参数,而是调用函数并返回其结果。
以下是 Scala 和 Java 的完整示例:
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
import java.util.function.Supplier;
import static java.lang.System.out;
public class Test {
public static void main(String... args) {
Buul.from(2 < 3).ifThenElse(
() -> { out.println("2 is less than 3"); },
() -> { out.println("2 is greater than 3"); }
);
// 2 is less than 3
}
}
interface Buul {
<T, U extends T, V extends T> T ifThenElse(Supplier<U> thn, Supplier<V> els);
void ifThenElse(CodeBlock thn, CodeBlock els);
Buul and(Supplier<Buul> other);
Buul or(Supplier<Buul> other);
Buul not();
static Buul from(boolean b) { return b ? new Tru() : new Fls(); }
static class Tru implements Buul {
@Override public <T, U extends T, V extends T> T ifThenElse(Supplier<U> thn, Supplier<V> els) { return thn.get(); }
@Override public void ifThenElse(CodeBlock thn, CodeBlock els) { thn.execute(); }
@Override public Buul and(Supplier<Buul> other) { return other.get(); }
@Override public Tru or(Supplier<Buul> other) { return this; }
@Override public Fls not() { return new Fls(); }
}
static class Fls implements Buul {
@Override public <T, U extends T, V extends T> T ifThenElse(Supplier<U> thn, Supplier<V> els) { return els.get(); }
@Override public void ifThenElse(CodeBlock thn, CodeBlock els) { els.execute(); }
@Override public Fls and(Supplier<Buul> other) { return this; }
@Override public Buul or(Supplier<Buul> other) { return other.get(); }
@Override public Tru not() { return new Tru(); }
}
}
@FunctionalInterface interface CodeBlock { void execute(); }
这如何适用于Option?
好的,太好了。我写了一篇关于如何使用消息调度实现布尔值的教程。这和Option 有什么关系?嗯,首先,我想在更笼统的层面回答你的问题:
使用选项类型是否需要 if 语句?
我想表明您可以始终通过引入适当的类型来消除对if 的需要!这适用于所有类型的条件(if、三元条件运算符?:、switch,甚至for 循环、while 循环和foreach 样式循环) 在所有面向对象的语言中。不仅适用于Option,也不仅仅适用于 Java 和 Scala。
我想首先向您展示更熟悉的布尔示例的另一个原因是,Option 的实现实际上看起来非常相似!
您有一个名为Option 的抽象基类/interface,并且您有两个具体的实现子类Some 和None。此接口具有允许您转换或操作值的方法,并且这些方法的实现使得None 子类中的实现基本上是 NO-OP。这样,您可以简单地调用这些方法,它们会做某事或什么都不做,但它们不会失败或抛出NullReferenceError 或类似的东西。让我们再次尝试一个简化的示例:
sealed abstract trait Opt[+T] {
def map[U](fn: T => U): Opt[U] // transform value
def apply(block: T => Unit): Unit // perform side-effect
def getOrElse[U >: T](other: U): U // if you want to get the value out, you need to provide a fallback
}
case class Yep[+T](value: T) extends Opt[T] {
override def map[U](fn: T => U) = Yep(fn(value))
override def apply(block: T => Unit) = block(value)
override def getOrElse[U >: T](other: U) = value
}
case object Nope extends Opt[Nothing] {
override def map[U](fn: Nothing => U) = this
override def apply(block: Nothing => Unit) = ()
override def getOrElse[U](other: U) = other
}
val presentValue = Yep(2)
presentValue(println)
// 2
presentValue.getOrElse(42) //=> 2
val missingValue = Nope
missingValue(println) // nothing happens
missingValue.getOrElse(42) //=> 42
再一次在 Java 中:
import java.util.function.Function;
import java.util.function.Consumer;
import static java.lang.System.out;
public class Test {
public static void main(String... args) {
Opt<Integer> presentValue = new Opt.Yep<>(2);
presentValue.perform(out::println);
// 2
out.println(presentValue.getOrElse(42)); // 2
Opt<Integer> missingValue = new Opt.Nope<>();
missingValue.perform(out::println); // nothing happens
out.println(missingValue.getOrElse(42)); // 42
}
}
interface Opt<T> {
<U> Opt<U> map(Function<T, U> fn);
void perform(Consumer<T> block);
T getOrElse(T other);
static class Yep<T> implements Opt<T> {
private final T value;
Yep(T value) { this.value = value; }
@Override public <U> Yep<U> map(Function<T, U> fn) { return new Yep<>(fn.apply(value)); }
@Override public void perform(Consumer<T> block) { block.accept(value); }
@Override public T getOrElse(T other) { return value; }
}
static class Nope<T> implements Opt<T> {
@Override public <U> Nope<U> map(Function<T, U> fn) { return new Nope<>(); }
@Override public void perform(Consumer<T> block) { return; }
@Override public T getOrElse(T other) { return other; }
}
}
另一种看待它的方式:Option as Collection
一种完全不同(但在某种意义上非常相似)的看待它的方式是将Option解释为Collection,它可以只有没有元素或只有一个元素。如果你想一想,那就是它,对吧?它是一个空的或具有单个元素的集合。那么,如果我们让Option 实现标准的收集操作/API 会发生什么?
好吧,例如,我们可以对其进行迭代:当你迭代一个列表时,你并不关心列表中有多少元素。你没有if 声明“如果有十个元素,运行循环体十次,如果有九个元素,运行循环体九次,......”。您只需拥有循环体。如果有十个元素,则执行十次,如果有两个元素,则执行两次,如果有一个元素,则执行一次,如果没有元素,则根本不执行。换句话说:如果您只是在Option 上使用foreach,它将完全按照您问题中假设的if 语句的作用...... 除了没有if 语句!
像这样:
for (int i: maybeInt) out.println(i) // "iterate"
事实上,您可能已经注意到,我上面的perform 方法(Scala 示例中的apply)实际上是foreach 的实现!
当然,我已经暗示了这种解释,通过将转换方法命名为上面的map。
通过使Option 同构为单元素集合,您可以打开集合库处理潜在缺失值的全部功能:您可以“迭代”Option(这将执行一个侧面-效果与否),您可以“转换”它(这将返回新值的Some,或者只是保持None,但不会像null那样抛出异常),您可以“展平”它(这将返回一个单级Some(例如Some(Some(Some(23))) → Some(23)),如果你有一个Somes的嵌套塔,或者None,如果有一个None塔(例如Some(Some(Some(None))) → None)),你可以flatMap它(它允许你安全地“链接”转换),等等。
但是等等……还有更多!
最后但同样重要的是:Option 作为 monad
Option 不仅是一个集合,它还是一个 monad!在这个已经太长的答案中,我不会介绍 monad 是什么。
Option 作为 monad 给我们带来的好处之一是能够“链接”可选值的计算。事实上,上面提到的map 和flatMap 是正确实施后使Option 成为monad 的操作。几种语言具有用于计算和链式计算单值的内置语法糖:Haskell 有 do-notation,Scala 有 for-comprehensions,C♯ 有 LINQ 查询表达式。
例如,在 Scala 中,您可以这样说:
for (i ← maybeInt) println(i) // "iterate"
for (i ← maybeInt) yield i * i // "transform"
在 C♯ 中:
from i in maybeInt select Console.WriteLine(i) // "iterate"
from i in maybeInt select i * i // "transform"
Daniel Westheide 的这篇文章也很好地介绍了 Option 作为集合和单子的解释: