簡體   English   中英

選項[T]類有什么意義?

[英]What is the point of the class Option[T]?

我無法理解Scala中Option[T]類的要點。 我的意思是,我不能看到任何advanages Nonenull

例如,考慮代碼:

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 ,然后在main第一行display的調用綁定為NPE失敗。 同樣,如果getPerson2返回None ,則display調用將再次失敗並出現類似的錯誤。

如果是這樣,那么為什么Scala通過引入一個新的值包裝器( Option[T] )而不是遵循Java中使用的簡單方法來使事情復雜化?

更新:

我根據@Mitch的建議編輯了我的代碼。 我仍然無法看到Option[T]任何特殊優勢。 我必須在兩種情況下測試異常nullNone :(

如果我從@ Michael的回復中正確理解, Option[T]的唯一優勢是它明確地告訴程序員這個方法可以返回None嗎? 這是設計選擇背后的唯一原因嗎?

如果你強迫自己永遠不會使用get ,你會更好地Option 那是因為get相當於“ok,把我送回null-land”。

所以,舉個你的例子吧。 如果不使用get你會如何調用display 以下是一些替代方案:

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沒有告訴你,你的代碼應該怎么寫。 它可能會輕輕地刺激你,但如果你想回到沒有安全網,這是你的選擇。


你在這里釘了它:

Option [T]的唯一優點是它明確地告訴程序員這個方法可以返回None嗎?

除了“唯一”。 但讓我以另一種方式重申: Option[T]對於T主要優點是類型安全。 它確保您不會將T方法發送到可能不存在的對象,因為編譯器不會讓您。

你說你必須在兩種情況下測試可空性,但是如果你忘記 - 或者不知道 - 你必須檢查null,編譯器會告訴你嗎? 或者你的用戶?

當然,由於它與Java的互操作性,Scala像Java一樣允許空值。 因此,如果您使用Java庫,如果您使用寫得不好的Scala庫,或者如果您使用寫得不好的個人 Scala庫,那么您仍然需要處理空指針。

Option I可以想到的其他兩個重要優點是:

  • 文檔:方法類型簽名將告訴您是否始終返回對象。

  • Monadic可組合性。

后者需要更長時間才能完全理解,並且它不適合簡單的示例,因為它只顯示其在復雜代碼上的強度。 所以,我將在下面給出一個例子,但我很清楚,除了那些已經獲得它的人之外,它幾乎沒有任何意義。

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)

monadic屬性bind作為map函數出現在Scala中,允許我們對對象進行鏈接操作,而不必擔心它們是否為“null”。

再舉一個這個簡單的例子吧。 假設我們想找到所有人喜愛的顏色。

// 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)

我希望這能說明選擇如何讓生活變得更輕松。

差異很微妙。 請記住,要真正成為一個函數,它必須返回一個值 - 在這個意義上,null實際上並不被認為是“正常的返回值”,更多的是底層類型 /沒有。

但是,在實際意義上,當你調用一個可選擇返回某個東西的函數時,你會這樣做:

getPerson2 match {
   case Some(person) => //handle a person
   case None => //handle nothing 
}

當然,你可以用null做類似的東西 - 但是這使得調用getPerson2的語義明顯地因為它返回了Option[Person] (一個很好的實用的東西,除了依賴於某人閱讀文檔並獲得NPE,因為他們不要閱讀文檔)。

我會嘗試挖掘一個功能強大的程序員,他能給出比我更嚴格的答案。

對我來說,在理解語法處理時,選項非常有趣。 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

如果任何fathersMothersSisterNone ,則fathersMothersSister將為None但不會引發NullPointerException 然后,您可以安全地將fathersMothersSister傳遞給一個使用Option參數的函數,而不必擔心。 所以你不檢查null,你不關心異常。 將其與synesso示例中提供的java版本進行比較。

您可以使用Option具有非常強大的組合功能:

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") )

也許其他人指出了這一點,但我沒有看到它:

使用Option [T]與null檢查進行模式匹配的一個優點是Option是一個密封類,因此如果您忽略編寫Some或None情況的代碼,Scala編譯器將發出警告。 編譯器有一個編譯器標志,可以將警告轉換為錯誤。 因此,可以防止在編譯時而不是在運行時處理“不存在”的情況。 與使用空值相比,這是一個巨大的優勢。

它沒有幫助避免空檢查,它是強制空檢查。 當你的類有10個字段時,這一點就變得清晰了,其中兩個字段可能為null。 您的系統還有50個其他類似的類。 在Java世界中,您嘗試使用mental horesepower,命名約定或甚至注釋的某些組合來阻止這些字段上的NPE。 每個Java開發人員都在這方面失敗了很多。 Option類不僅使任何試圖理解代碼的開發人員在視覺上清楚地看到“可空”值,而且允許編譯器強制執行此先前未說明的合同。

[從Daniel Spiewak的 評論中復制]

如果使用Option的唯一方法是模式匹配以獲取值,那么是的,我同意它並沒有完全改進null。 但是,你錯過了一個*巨大的*類功能。 使用Option的唯一令人信服的理由是,如果您正在使用其高階效用函數。 實際上,你需要使用它的monadic性質。 例如(假設一定量的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 -comprehensions:

 val value = for { row <- database fetchRowById 42 key <- row get "port_key" value <- myMap get key } yield value val result = value getOrElse defaultValue 

您會注意到我們*從不*明確檢查null,None或其任何類似。 Option的重點是避免任何檢查。 你只需將計算字符串串聯起來並向下移動,直到你真的*需要得到一個值。 在這一點上,你可以決定你是否願意做明確的檢查(這你應該這樣做),提供默認值,拋出異常等。

我從來沒有對Option做任何明確的匹配,而且我知道很多其他Scala開發人員都在同一條船上。 前幾天David Pollak向我提到他在Option (或Box ,在Lift的情況下)上使用這種顯式匹配作為一個標志,表明編寫代碼的開發人員並不完全理解該語言及其標准庫。

我不是故意成為一個巨魔錘,但你真的需要看看語言功能在實踐中如何實際*在你將它們打成無用之前使用。 我絕對同意Option是非常不引人注目的,因為*你*使用它,但你沒有按照設計的方式使用它。

這里沒有其他人似乎提出的一點是,雖然你可以有一個空引用,但是Option引入了一個區別。

那就是你可以Option[Option[A]] ,它將由NoneSome(None)Some(Some(a))居住,其中aA的常見居民之一。 這意味着如果你有某種容器,並希望能夠在其中存儲空指針並將它們取出,你需要傳回一些額外的布爾值來知道你是否真的得到了一個值。 這樣的疣在java容器API中比比皆是 ,而一些無鎖的變體甚至無法提供它們。

null是一次性構造,它不與自身構成,它僅適用於引用類型,並且它迫使您以非完全方式進行推理。

例如,當你檢查

if (x == null) ...
else x.foo()

你必須在x != nullelse分支中x != null攜帶,並且已經檢查過了。 但是,在使用類似選項的東西時

x match {
case None => ...
case Some(y) => y.foo
}

知道 y不是None建築 - 而且你知道它也不是null ,如果不是因為Hoare 十億美元的錯誤

再加上Randall的答案預告片 ,理解為什么Option可能缺少某個值,需要了解哪些Option與Scala中的許多其他類型共享,特別是類型建模monad。 如果一個代表缺少null值,那么缺席 - 存在區分不能參與其他monadic類型共享的契約。

如果您不知道monad是什么,或者您沒有注意到它們在Scala庫中的表現方式,那么您將無法看到Option與之一起播放的內容,並且您無法看到您錯過的內容。 有使用很多好處Option ,而不是零,這將是值得關注的,甚至在沒有任何單子的概念(我討論其中一些在“選項的成本/有些VS空” 斯卡拉用戶郵件列表線程這里 ),但是說起關於它隔離有點像談論一個特定的鏈表實現的迭代器類型,想知道為什么它是必要的,一直錯過更通用的容器/迭代器/算法接口。 這里還有一個更廣泛的界面,而Option提供了該界面的存在和缺席模型。

Option [T]是一個monad,當你使用高階函數來操作值時,它非常有用。

我建議你閱讀下面列出的文章,它們是非常好的文章,向你展示為什么Option [T]是有用的,它如何在功能方面使用。

空返回值僅用於與Java兼容。 否則你不應該使用它們。

我認為密鑰可以在Synesso的答案中找到:Option 不是主要用作null的繁瑣別名,而是作為一個完整的對象,可以幫助你解決邏輯問題。

null的問題在於它缺少一個對象。 它沒有可以幫助你處理它的方法(盡管作為一個語言設計師,你可以為你的語言添加越來越長的功能列表,如果你真的喜歡它就模擬一個對象)。

正如您所演示的,Option可以做的一件事就是模仿null; 然后你必須測試非常值“無”而不是非常值“null”。 如果你忘了,在任何一種情況下,都會發生不好的事情。 Option確實不太可能偶然發生,因為你必須輸入“get”(它應該提醒你它可能是 null,呃,我的意思是None),但這是一個小的好處,以換取額外的包裝器對象。

選項真正開始顯示它的力量正在幫助你處理我想要的東西 - 但是 - 我實際上沒有 - 的概念。

讓我們考慮一些你可能想要做的事情,可能是空的。

如果你有一個null,也許你想設置一個默認值。 讓我們比較Java和Scala:

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,Daniel和范例中的示例所示。

這不是一個巨大的改進,但是如果你把所有內容都添加起來,那么除了高性能代碼之外,它還是值得的(你想要避免創建Some(x)包裝器對象的微小開銷)。

除了作為提醒你null / None情況的設備之外,匹配用法本身並沒有那么有用。 當它真正有用的時候是你開始鏈接它,例如,如果你有一個選項列表:

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.")
}

現在,您可以在一個方便的語句中將None情況和List-is-empty案例一起折疊,從而准確地提取出您想要的值。

這真是一個編程風格的問題。 使用Functional Java,或者編寫自己的幫助器方法,可以使用Option功能但不放棄Java語言:

http://functionaljava.org/examples/#Option.bind

僅僅因為Scala默認包含它並不會使它變得特別。 功能語言的大多數方面都可以在該庫中使用,它可以與其他Java代碼很好地共存。 就像您可以選擇使用空值編寫Scala一樣,您可以選擇在沒有它們的情況下編寫Java。

有明確的選項類型的真正優勢是,你可以在任何地方的98%, 使用它們,因此靜態排除空例外。 (在另外2%中,類型系統會提醒您在實際訪問它們時進行正確檢查。)

提前承認這是一個明智的答案,Option是一個monad。

其實我和你有同感。 關於Option它真的困擾我1)有一個性能開銷,因為每一個都創建了一些“一些”包裝器。 2)我必須在我的代碼中使用很多Some和Option。

因此,要了解這種語言設計決策的優缺點,我們應該考慮其他選擇。 由於Java只是忽略了可空性的問題,因此它不是替代方案。 實際的替代方案提供了Fantom編程語言。 那里有可空和不可空的類型和? ?:運算符而不是Scala的map / flatMap / getOrElse。 我在比較中看到以下項目符號:

期權的優勢:

  1. 更簡單的語言 - 無需額外的語言結構
  2. 與其他monadic類型的制服

Nullable的優勢:

  1. 典型情況下的語法較短
  2. 更好的性能(因為你不需要為map創建新的Option對象和lambdas,flatMap)

所以這里沒有明顯的贏家。 還有一點需要注意。 使用Option沒有主要的語法優勢。 你可以定義類似的東西:

def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)

或者使用一些隱式轉換來獲得帶點的pritty語法。

Option工作的另一種情況是在類型不能具有空值的情況下。 無法在Int,Float,Double等值中存儲null,但使用Option可以使用None。

在Java中,您需要使用這些類型的盒裝版本(Integer,...)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM