Option[T] 类的意义是什么?
-
21-09-2019 - |
题
我无法理解这一点 Option[T]
Scala 中的类。我的意思是,我看不到任何优点 None
超过 null
.
例如,考虑以下代码:
object Main{
class Person(name: String, var age: int){
def display = println(name+" "+age)
}
def getPerson1: Person = {
// returns a Person instance or null
}
def getPerson2: Option[Person] = {
// returns either Some[Person] or None
}
def main(argv: Array[String]): Unit = {
val p = getPerson1
if (p!=null) p.display
getPerson2 match{
case Some(person) => person.display
case None => /* Do nothing */
}
}
}
现在假设,该方法 getPerson1
回报 null
, ,然后调用 display
在第一行 main
必然会失败 NPE
. 。同样如果 getPerson2
回报 None
, , 这 display
调用将再次失败并出现一些类似的错误。
如果是这样,那么为什么 Scala 会通过引入新的值包装器来使事情变得复杂(Option[T]
)而不是遵循 Java 中使用的简单方法?
更新:
我已经按照以下方式编辑了我的代码 @米奇的建议。我仍然看不出任何特别的优势 Option[T]
. 。我必须测试特殊情况 null
或者 None
在这两种情况下。:(
如果我理解正确的话 @迈克尔的回复, 是唯一的优点 Option[T]
是它明确地告诉程序员 该方法可能返回 None?这是这种设计选择背后的唯一原因吗?
解决方案
你会明白的 Option
如果你强迫自己永远、永远不要使用,那就更好了 get
. 。那是因为 get
相当于“好吧,送我回零境”。
所以,以你的例子为例。你会怎么打电话 display
不使用 get
?以下是一些替代方案:
getPerson2 foreach (_.display)
for (person <- getPerson2) person.display
getPerson2 match {
case Some(person) => person.display
case _ =>
}
getPerson2.getOrElse(Person("Unknown", 0)).display
这些替代方案都不会让你打电话 display
在不存在的东西上。
至于为什么 get
存在,Scala 不会告诉你代码应该如何编写。它可能会轻轻地刺激你,但如果你不想回到安全网,那是你的选择。
你把它钉在这里:
选项[t]的唯一优点是它明确地告诉程序员此方法可以返回?
除了“唯一”。但让我用另一种方式重申这一点:这 主要的 的优点 Option[T]
超过 T
是类型安全。它确保您不会发送 T
方法指向可能不存在的对象,因为编译器不允许您这样做。
您说您必须在这两种情况下测试可空性,但是如果您忘记 - 或者不知道 - 您必须检查是否为空,编译器会告诉您吗?或者你的用户会吗?
当然,由于它与 Java 的互操作性,Scala 就像 Java 一样允许 null。所以如果你使用Java库,如果你使用写得不好的Scala库,或者如果你使用写得不好的库 个人的 Scala 库,您仍然需要处理空指针。
另外两个重要优点 Option
我能想到的有:
文档:方法类型签名将告诉您是否始终返回对象。
单子可组合性。
后一种方法需要更长的时间才能完全理解,并且它不太适合简单的示例,因为它只在复杂的代码上显示出其优势。所以,我将在下面给出一个例子,但我很清楚,除了已经得到它的人之外,它几乎没有任何意义。
for {
person <- getUsers
email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)
其他提示
比较
val p = getPerson1 // a potentially null Person
val favouriteColour = if (p == null) p.favouriteColour else null
使用:
val p = getPerson2 // an Option[Person]
val favouriteColour = p.map(_.favouriteColour)
在一元属性的结合,这似乎Scala中作为地图功能,允许我们链运作上的对象,而无需担心他们是否是“空”或没有。
一点进一步采取这个简单的例子。假设我们想找到的人一个列表的所有最喜欢的颜色。
// list of (potentially null) Persons
for (person <- listOfPeople) yield if (person == null) null else person.favouriteColour
// list of Options[Person]
listOfPeople.map(_.map(_.favouriteColour))
listOfPeople.flatMap(_.map(_.favouriteColour)) // discards all None's
或者我们想找到一个人的父亲的母亲的妹妹的名字:
// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister
// with options
val fathersMothersSister = getPerson2.flatMap(_.father).flatMap(_.mother).flatMap(_.sister)
我希望这棚上的选项如何让生活变得更轻松一些轻。
的差异是微妙的。请记住,是真正的功能它必须返回一个值 - 空是不是真的认为是“正常的返回值”,在这个意义上,更多的是的底部型 /无。
但是,在实际意义上,当你调用可选返回的东西的功能,你会怎么做:
getPerson2 match {
case Some(person) => //handle a person
case None => //handle nothing
}
当然,你可以用空类似的东西 - 但是这使得凭借它返回getPerson2
(一个漂亮实用的东西,其他的不是依靠别人读取DOC,却得到了NPE的事实调用Option[Person]
明显的语义,因为他们不读的文档)。
我会尝试和挖掘功能的程序员比我谁可以给一个更严格的答案。
对于我来说,当与理解语法处理选项是非常有趣的。以 synesso 前面的示例:
// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister
// with options
val fathersMothersSister = for {
father <- person.father
mother <- father.mother
sister <- mother.sister
} yield sister
如果任何分配的被None
,所述fathersMothersSister
将None
但没有NullPointerException
将升高。然后,您可以安全地通过fathersMothersSister
to函数采取选项的参数,而无需担心。所以你不检查空和你不在乎例外。与此相比,在所呈现的Java版本 synesso 例如
您有选项非常强大的组成功能:
def getURL : Option[URL]
def getDefaultURL : Option[URL]
val (host,port) = (getURL orElse getDefaultURL).map( url => (url.getHost,url.getPort) ).getOrElse( throw new IllegalStateException("No URL defined") )
也许别人指出了这一点,但我没有看到它:
与选项[T]与空检查是选项是密封类,因此,如果忽略了编码任一中的一些或无的情况下,Scala编译器将发出警告模式匹配的一个优点。有一个编译器标志将变成警告变为错误的编译器。因此,这可以防止故障在编译的时候,而不是在运行时处理“不存在”的情况。这是优于使用空值的巨大的优势。
这是不是有帮助避免空检查,它的存在强迫空校验。当你的类有10场,其中有两个可能是空的点变得清晰。而你的系统有其他类似50班。在Java世界里,你尽量避免在使用心理horesepower的某种组合在这些领域的NPE,命名约定,或者甚至注解。而每一个Java开发在这个没有一个显著的程度。 Option类不仅使得“可空”值直观清晰试图了解代码的开发人员,但允许编译器执行此之前潜合同。
如果唯一的方法是使用
Option
是为了使值拿出,是的,是的,我同意它并不能在零零上进行改进。但是,您缺少一个 *巨大的功能类别。使用的唯一令人信服的理由Option
如果您使用其高阶实用程序功能。有效地,您需要使用其单一的性质。例如(假设一定量的API修剪):val row: Option[Row] = database fetchRowById 42 val key: Option[String] = row flatMap { _ get “port_key” } val value: Option[MyType] = key flatMap (myMap get) val result: MyType = value getOrElse defaultValue
是不是很漂亮?如果我们使用的话,我们实际上可以做得更好
for
-理解:val value = for { row <- database fetchRowById 42 key <- row get "port_key" value <- myMap get key } yield value val result = value getOrElse defaultValue
您会注意到,我们 *从不 *明确检查null,没有或任何一个同类。选项的全部要点是避免任何检查。您只需将计算字符串计算并向下移动,直到您 *真的 *需要提出一个值。那时,您可以决定是否要进行明确检查(您应该 绝不 必须这样做),提供默认值,引发异常等。
我从来没有做过任何明确的匹配
Option
, ,而且我知道许多其他在同一条船上的Scala开发人员。大卫·波拉克(David Pollak)在前几天向我提到他,他在Option
(或者Box
, ,在提升的情况下)表示编写代码的开发人员并不完全理解该语言及其标准库。我并不是要成为巨魔锤,但是您真的需要查看语言功能在练习中实际上是如何使用的,然后才能将其作为毫无用处。我绝对同意, *您 *使用它,但您并没有按照它的设计方式使用它。
这里似乎没有其他人提出的一点是,虽然您可以拥有空引用,但 Option 引入了一种区别。
那就是你可以拥有 Option[Option[A]]
, ,其中将居住着 None
, Some(None)
和 Some(Some(a))
在哪里 a
是常住居民之一 A
. 。这意味着,如果您有某种容器,并且希望能够在其中存储空指针并将其取出,则需要传回一些额外的布尔值以了解是否确实取出了值。疣是这样的 盛产 在java容器API和一些无锁变体中甚至无法提供它们。
null
是一种一次性构造,它不会与自身组合,它仅可用于引用类型,并且它迫使您以非整体方式进行推理。
例如,当您检查
if (x == null) ...
else x.foo()
你必须在整个过程中牢记在心 else
分支那个 x != null
并且这已经被检查过。但是,当使用诸如选项之类的东西时
x match {
case None => ...
case Some(y) => y.foo
}
你 知道 y 不是 None
通过构造——你会知道它不是 null
要么,如果不是霍尔的 十亿美元的错误.
Option[T] 是一个 monad,当您使用高阶函数来操作值时,它非常有用。
我建议您阅读下面列出的文章,它们都是非常好的文章,向您展示了为什么 Option[T] 有用以及如何以功能方式使用它。
如果你不知道什么单子,或者如果你没有注意到他们是如何在Scala的图书馆代表,你不会看到有什么沿Option
戏,你不能看到你在做什么错过了。有使用的,而不是零,这将是值得关注的,甚至在没有任何单子概念Option
很多好处(我讨论其中的一些在斯卡拉用户的邮件“选项/有些VS零成本”名单线程这里),但说起它隔离是一种像在谈论一个特定的链表实现的迭代器类型,不知道为什么这是必要的,所有的,而更普遍的容器/迭代器/算法接口错过了。有在工作中更广泛的接口这里也和Option
提供了接口的存在和 - 不存在模型。
我觉得关键是在Synesso的回答中发现:选项是的不的主要可用作空繁琐的别名,但作为一个成熟的对象,就可以帮助您与您的逻辑<。 / p>
用空的问题是,它是一个对象的缺乏。它有没有方法可以帮助你处理它(虽然作为一个语言设计者可以添加的功能,以您的语言模仿的对象越来越长的列表,如果你真的喜欢它的感觉)。
一件事选项可以做,因为你已经证明,是模拟空;那么你要测试的非凡价值“无”,而不是价值非凡“空”。如果你忘了,在这两种情况下,不好的事情会发生。选项并使其不太可能是偶然发生的,因为你必须键入“get”(这应该提醒你,它的可能的空,呃,我的意思是无),但是这是一个小的好处以交换额外的包装对象。
其中选项真正开始显示它的力量帮助你对付我,想出头,但是,我 - 不要 - 实际上,有一概念。
让我们考虑你可能想的东西,可能是空做一些事情。
也许你想设置的默认值,如果你有一个空。让我们比较一下Java和斯卡拉:
String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"
在地方的有些繁琐:构建我们有与“使用默认值,如果我空”的理念有关系的方法。这种清理你的代码一点点。
也许你想只有当你有一个真正的价值,创建新的对象。比较:
File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))
Scala是稍短,并再次避免了故障源。当你需要链的东西放在一起为在Synesso,丹尼尔,并且例证实施例中所示,然后考虑的累积优点。
这不是一个的广大的改善,但如果添加了一切,这是值得它无处不在,保存非常高性能的代码(如果你想避免,甚至创造一些小小的开销(x)的包装对象)。
在配合使用是不是真的,除了作为一个设备来提醒您空/无的情况下对自己有帮助的。当它是真正有用的是,当你开始链接吧,例如,如果你有一个选项列表:
val a = List(Some("Hi"),None,Some("Bye"));
a match {
case List(Some(x),_*) => println("We started with " + x)
case _ => println("Nothing to start with.")
}
现在你到折叠无案件和列表的是空在翻出正是你想要的值一个方便的声明的情况下都在一起。
空返回值仅存在用于与Java兼容。你不应该以其他方式使用它们。
这是一个真正的编程风格的问题。使用Java功能,或通过编写自己的辅助方法,你可以有你的选项功能,但不会放弃Java语言:
http://functionaljava.org/examples/#Option.bind
仅仅因为Scala包括它在默认情况下不会使他与众不同。函数式语言的许多方面在库中可用,它可以与其他Java代码很好地共存。就像你可以选择与斯卡拉空程序,你可以选择没有他们的Java编程
提前,它是一个圆滑的解释承认,选项是一个单子。
其实我也有同样的疑问。关于选项,它真的让我很困扰:1)存在性能开销,因为到处都有创建的“一些”包装器。2)我必须在我的代码中使用很多Some和Option。
因此,为了了解这种语言设计决策的优点和缺点,我们应该考虑替代方案。由于 Java 只是忽略了可空性问题,因此它不是替代方案。实际的替代方案是 Fantom 编程语言。那里有可为空和不可为空的类型和 ?。?:运算符而不是 Scala 的 map/flatMap/getOrElse。我在比较中看到以下项目符号:
选项的优点:
- 更简单的语言 - 不需要额外的语言结构
- 与其他一元类型一致
Nullable 的优点:
- 典型情况下更短的语法
- 更好的性能(因为您不需要为 map、flatMap 创建新的 Option 对象和 lambda)
所以这里没有明显的赢家。还有一点注意。使用 Option 没有主要的语法优势。您可以定义如下内容:
def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)
或者使用一些隐式转换来获得带有点的简洁语法。
有明确的选项类型的真正优势是,你可以的不的使用他们在所有地方的98%,因此,静态排除空例外。 (而在其他2%类型系统提醒您正确检查当你真正访问它们。)
,其中选件上工作的另一种情况,是在类型不能够有一个空值的情况。这是不可能为null存储在一个整型,浮点型,双等价值,但有一个选项可以使用无。
在Java中,你需要使用这些类型的盒装版本(整数,...)。