为什么 Some(x).map(_ => null) 不计算为 None?

Why does Some(x).map(_ => null) not evaluate to None?

我最近在 Scala 中遇到了一个令人困惑的问题。我希望以下代码会导致 None,但会导致 Some(null):

Option("a").map(_ => null)

这背后的原因是什么?为什么它不会导致 None

注意:此问题不是 Why Some(null) isn't considered None? 的重复问题,因为该问题要求明确使用 Some(null)。我的问题是关于使用 Option.map.

这里是 code for Option map method:

/** Returns a $some containing the result of applying $f to this $option's
 * value if this $option is nonempty.
 * Otherwise return $none.
 *
 *  @note This is similar to `flatMap` except here,
 *  $f does not need to wrap its result in an $option.
 *
 *  @param  f   the function to apply
 *  @see flatMap
 *  @see foreach
 */
@inline final def map[B](f: A => B): Option[B] =
  if (isEmpty) None else Some(f(this.get))

因此,如您所见,如果选项不为空,它将映射到 Some 函数返回的值。这是 code for Some class:

/** Class `Some[A]` represents existing values of type
 *  `A`.
 *
 *  @author  Martin Odersky
 *  @version 1.0, 16/07/2003
 */
@SerialVersionUID(1234815782226070388L) // value computed by serialver for 2.11.2, annotation added in 2.11.4
final case class Some[+A](x: A) extends Option[A] {
  def isEmpty = false
  def get = x
}

因此,如您所见,Some(null) 实际上会创建一个包含 nullSome 对象。您可能想要做的是使用 Option.apply,如果值为 null,它会执行 returns 和 None。这是 code for Option.apply method:

/** An Option factory which creates Some(x) if the argument is not null,
 *  and None if it is null.
 *
 *  @param  x the value
 *  @return   Some(value) if value != null, None if value == null
 */
def apply[A](x: A): Option[A] = if (x == null) None else Some(x)

因此,您需要这样编写代码:

Option("a").flatMap(s => Option.apply(null))

当然,这段代码没有任何意义,但我会认为你只是在做某种实验。

为什么会是None,map的签名是一个从值AB产生Option[B]的函数。在那个签名中没有任何地方通过说 B 是一个选项 [B] 来表明 B 可能是 nullflatMap 但是确实表明返回的值也是可选的。它的签名是 Option[A] => (A => Option[B]) => Option[B].

Optionnull 的一种替代品,但通常当您与某些 java 代码交谈时,您会在 scala 中看到 null,它不像Option 应该尽可能处理 nulls,它不是设计用来与 nulls 一起使用的,而是用来代替它们的。然而,有一种方便的方法 Option.apply 类似于 java 的 Optional.ofNullable 可以处理 null 的情况,而这主要是关于 nullsOptions 在 Scala 中。在所有其他情况下,它在 SomeNone 上工作,无论 null 是否在内部都没有任何区别。

如果您有一些来自 java 的令人讨厌的方法 returning null 并且您想直接使用它,请使用以下方法:

def nastyMethod(s: String): String = null

Some("a").flatMap(s => Option(nastyMethod(s)))
// or
Some("a").map(nastyMethod).flatMap(Option(_))

两者输出Option[String] = None

因此,nastyMethod 可以 return 一个 Stringnull 在概念上是一个 Option,因此将其结果包装在一个 Option 中并将其用作 Option。不要指望 null 魔法会在您需要的时候出现。

每次我们向规则添加例外时,我们就剥夺了自己推理代码的工具。

Some 上的映射始终计算为 Some。这是一个简单而有用的法律。如果我们要进行您提议的更改,我们将不再拥有该法律。例如,这是我们可以肯定地说的一件事。对于所有 fxy

Some(x).map(f).map(_ => y) == Some(y)

如果我们要进行您提议的更改,那么该声明将不再正确;具体来说,它不适用于 f(x) == null.

的情况

而且,Option是一个functor。 Functor 是对具有 map 函数的事物的有用概括,并且它具有与映射应该如何工作的直觉相符的规律。如果我们要进行您建议的更改,Option 将不再是函子。

null 是 Scala 中的一个异常,它的存在仅仅是为了与 Java 库的互操作性。放弃 Option 作为仿函数的有效性不是一个好的理由。

为了理解这是怎么回事,我们可以使用函数替换原理来一步步探索给定的表达式:

Option("a").map(s => null) // through Option.apply
Some("a").map(s => null) // let's name the anonymous function as: f(x) = null 
Some("a").map(x => f(x))  // following Option[A].map(f:A=>B) => Option[B]
Some(f("a"))  // apply f(x)
Some(null)

问题中表达的混淆来自假设 map 将适用于 Option 之前 Option.apply 的论点] 被评估:让我们看看它怎么可能行得通:

Option("a").map(x=> f(x)) // !!! can't evaluate map before Option.apply. This is the key to understand !
Option(f(a))              // !!! we can't get here
Option(null)              // !!! we can't get here
None                      // !!! we can't get here