Option [T]クラスのポイントは何ですか?


83

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行われた呼び出しは、displaymain失敗することになりNPEます。同様に、getPerson2が返されたNone場合、display呼び出しは再び失敗し、同様のエラーが発生します。

もしそうなら、なぜScalaOption[T]はJavaで使用される単純なアプローチに従うのではなく、新しい値ラッパー()を導入することによって物事を複雑にするのですか?

更新:

@Mitchの提案に従ってコードを編集しました。私はまだの特定の利点を見ることができませんOption[T]。例外的なnull場合None、または両方の場合をテストする必要があります。:(

@Michaelの返信から正しく理解した場合、唯一の利点はOption[T]このメソッドがNoneを返す可能性があることをプログラマーに明示的に伝えることです。これがこのデザインの選択の背後にある唯一の理由ですか?


23
実際、Option [T]の「get」メソッドは「なぜこれにパターンマッチングしないのですか?」と発音されます。
Mitch Blevins 2010年

2
ミッチは正しい。使用せずに、あなたの例を言い換えるてみてくださいget、あなたはよ得ることを。:-)
Daniel C. Sobral 2010年

あなたはJavaであるPersonp ..を持っています... try val p = ...また、以下のDanielとSynessoによって示されているように、Optionにはさらに多くのものがあります-ここにいくつかの素晴らしい答えがあります。
Michael Neale 2010年

@マイケル:おっと!指さしてくれてありがとう。修正しました。
missingfaktor 2010年

回答:


72

Optionを絶対に使用しないように強制すると、より良いポイントが得られますget。これgetは、「OK、null-landに返送してください」と同等であるためです。

だから、あなたのその例を見てください。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はコードの記述方法を教えてくれません。それはあなたを優しく嘲笑するかもしれません、しかしあなたがセーフティネットなしにフォールバックしたいなら、それはあなたの選択です。


あなたはここにそれを釘付けにしました:

Option [T]の唯一の利点は、このメソッドがNoneを返す可能性があることをプログラマーに明示的に伝えることです。

「のみ」を除いて。しかし、別の言い方をすれば、overの主な利点は型の安全性です。コンパイラーでは許可されないため、存在しない可能性のあるオブジェクトにメソッドを送信しないようにします。Option[T]TT

どちらの場合もnull可能性をテストする必要があるとおっしゃいましたが、nullを忘れた場合、またはわからない場合は、nullをチェックする必要があります。コンパイラは教えてくれますか?それともユーザーですか?

もちろん、Javaとの相互運用性のため、ScalaはJavaと同じようにnullを許可します。したがって、Javaライブラリを使用する場合、不適切に記述されたScalaライブラリを使用する場合、または不適切に記述された個人用Scalaライブラリを使用する場合でも、nullポインタを処理する必要があります。

Option私が考えることができる他の2つの重要な利点は次のとおりです。

  • ドキュメント:メソッドタイプのシグネチャは、オブジェクトが常に返されるかどうかを示します。

  • モナドの構成可能性。

後者は完全に理解するのにはるかに長い時間がかかり、複雑なコードでのみその強みを示すため、単純な例にはあまり適していません。それで、以下に例を挙げますが、それはすでにそれを手に入れている人々を除いてほとんど何も意味しないことを私はよく知っています。

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)

5
「絶対に絶対に使用しないでくださいget」->つまり、「あなたはgetそれをしません!」:)
fredoverflow 2013

31

比較:

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にmap関数として表示されるモナドプロパティbindを使用すると、オブジェクトが「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の場合はどうなりますか?mapが戻りNone、呼び出しはエラーで失敗します。nullアプローチよりもどのように優れていますか?
missingfaktor

5
いいえ。人がNone(または父、母、姉妹)の場合、fathersMothersSisterはNoneになりますが、エラーはスローされません。
パラダイム

6
マップではなく、flatMapを意味していると思います。
レトロニム2010年

ダニエルを編集してくれてありがとう。私はそれを投稿する前にコードを試しませんでした。次回はもっとうまくいくでしょう。
Synesso 2010年

2
val favouriteColour = if(p == null)p.favouriteColour else null // Optionが回避するのに役立つエラー!この答えは、誰もこのエラーを発見することなく、何年もここにありました!
マークリスター

22

違いは微妙です。真に関数であるために、値を返す必要があることに注意してください。nullは、その意味で「通常の戻り値」とは見なされず、ボトム型/ nothingです。

ただし、実際的な意味では、オプションで何かを返す関数を呼び出すと、次のようになります。

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

確かに、nullでも同様のことができますが、これにより、呼び出されるセマンティクスがgetPerson2返されるという事実によって明らかになりますOption[Person](ドキュメントを読んでいる人に頼って、NPEを取得していないため、NPEを取得する以外の実用的な方法です。 doc)。

私よりも厳密な答えを出すことができる関数型プログラマーを掘り下げてみます。


1
これは私のオプションの理解でもあります。これは、Noneを取得できることをプログラマーに明示的に伝えます。愚かで、Some(T)を実行することを覚えていても、Noneをキャッチできない場合は、問題が発生します。
cflewis 2010年

1
ルイスハム-Some / Noneが代数的データ型(抽象的な封印された特性...)を形成するので、コンパイラーは警告を出すと思います(しかし、私はここでメモリから移動します)。
Michael Neale 2010年

6
これを使用するほとんどの言語でのOptionタイプのポイントは、ランタイムnull例外の代わりに、コンパイル時タイプエラーが発生することです。コンパイラーは、データを使用するときにNone条件に対するアクションがないことを認識できます。タイプエラーになります。
ジャスティンスミス

15

私にとって、オプションは、理解構文のために処理されるときに本当に興味深いものです。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

割り当てのいずれかがである場合NonefathersMothersSisterは発生しますNoneが、発生しませんNullPointerException。その後、fathersMothersSister心配することなく、Optionパラメータを受け取る関数に安全に渡すことができます。したがって、nullをチェックせず、例外を気にしません。これを、synessoの例に示されているJavaバージョンと比較してください。


3
Scalaで<-構文が「リスト内包表記構文」に限定されているのは残念です。これはdo、Haskellのより一般的な構文またはdomonadClojureのモナドライブラリの形式と実際に同じだからです。それをリストに結びつけることはそれを短く売ります。
seh 2010年

11
Scalaの「内包表記」は本質的にHaskellの「do」であり、リストに限定されず、以下を実装するものなら何でも使用できます。defmap [B](f:A => B):C [B] def flatMap [B](f:A => C [B]):C [B] def filter(p:A =>ブール値):C [A]。IOW、任意のモナド
GClaramunt 2010年

2
@seh @GClaramuntのコメントに賛成しましたが、彼の主張を十分に強調することはできません。Scalaではfor-comprehensionsとlistsの間に関係はありません-後者が前者で使用できることを除いて。stackoverflow.com/questions/1052476/…を参照してください。
ダニエルC.ソブラル2010年

はい、は関係がないこと知っていますが、それは指摘する価値があることに同意します。私はこの回答の最初の行にコメントしていました。そこでは、パラダイムが「リスト内包構文」に言及しています。言語デザインの問題とは対照的に、それは教育の問題です。
seh 2010年

9

オプションを使用すると、非常に強力な構成機能が得られます。

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

これを完全に説明できますか?
Jesvin Jose 2013

8

他の誰かがこれを指摘したかもしれませんが、私はそれを見ませんでした:

Option [T]とnullチェックのパターンマッチングの利点の1つは、Optionが封印されたクラスであるため、SomeまたはNoneの場合のコーディングを怠ると、Scalaコンパイラーが警告を発行することです。警告をエラーに変えるコンパイラフラグがコンパイラにあります。したがって、実行時ではなくコンパイル時に「存在しない」ケースの処理に失敗するのを防ぐことができます。これは、null値を使用するよりも大きな利点です。


7

ヌルチェックを回避するためにあるのではなく、ヌルチェックを強制するためにあります。クラスに10個のフィールドがあり、そのうち2個がnullになる可能性がある場合、ポイントが明確になります。そして、あなたのシステムには他に50の同様のクラスがあります。Javaの世界では、メンタルホースパワー、命名規則、または注釈の組み合わせを使用して、これらのフィールドでNPEを防止しようとします。そして、すべてのJava開発者はこれでかなりの程度失敗します。Optionクラスは、コードを理解しようとしている開発者に「null許容」値を視覚的に明確にするだけでなく、コンパイラーがこの以前は口に出さなかったコントラクトを強制できるようにします。


6

[ DanielSpiewakによるこのコメントからコピー]

使用する唯一の方法Optionが値を取得するためにパターンマッチである場合、はい、それがnullを超えてまったく改善されないことに同意します。ただし、その機能の*巨大な*クラスが不足しています。使用する唯一の説得力のある理由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-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コードを書いた開発者は、完全に言語とその標準ライブラリを理解していないというサインとしてリフトの場合には、)。

私はトロールハンマーになるつもりはありませんが、言語機能が役に立たないものとしてバッシングする前に、実際にどのように使用されているかを実際に確認する必要があります。私は、Optionが*あなた*がそれを使用したので非常に説得力がないことに完全に同意しますが、あなたはそれを設計された方法で使用していません。


ここには悲しい結果があります。ジャンプベースの短絡が発生しないため、連続するすべてのステートメントOptionでがNone再度テストされます。ステートメントがネストされた条件として記述されている場合、潜在的な「失敗」はそれぞれ1回だけテストされ、対処されます。あなたの例では、の結果は3fetchRowById効果的に検査されます。1回はの初期化をガイドし、もう一度は's、最後に' sです。それを書くためのエレガントな方法ですが、実行時のコストがないわけではありません。keyvalueresult
seh 2010

4
あなたはScalaの理解を誤解していると思います。2番目の例は、明らかにループではなく、最初の例のように、コンパイラーによって一連のflatMap操作に変換されます。
ケビンライト

ここにコメントを書いてから久しぶりですが、ケビンさんを見たばかりです。ケビン、あなたが「あなたは誤解している」と書いたときに誰に言及していましたか?ループについては何も言及しなかったので、どうしてだったのかわかりません。
seh 2013

6

ここで他の誰も提起していないように思われる1つのポイントは、null参照を持つことはできますが、Optionによって導入された違いがあるということです。

それはあなたが持っている可能であるOption[Option[A]]が居住されるであろう、NoneSome(None)Some(Some(a))どこaの通常の住民の一つですA。つまり、ある種のコンテナがあり、その中にnullポインタを格納してそれらを取得できるようにしたい場合は、実際に値を取得したかどうかを知るために、追加のブール値を返す必要があります。このような疣贅はJavaコンテナAPIにたくさんあり、一部のロックフリーバリアントはそれらを提供することさえできません。

null は1回限りの構造であり、それ自体で構成されることはなく、参照型でのみ使用可能であり、完全ではない方法で推論する必要があります。

たとえば、チェックするとき

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

elseブランチ全体で頭の中で持ち歩く必要がx != nullあり、これはすでにチェックされています。ただし、オプションのようなものを使用する場合

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

あなたyがNone構造によるものではないことを知っています-そしてnullそれがHoareの10億ドルの間違いがなければ、それもそうではなかったことを知っているでしょう。


3

Option [T]はモナドであり、高階関数を使用して値を操作するときに非常に便利です。

以下にリストされている記事を読むことをお勧めします。これらは、Option [T]が有用である理由と、それを機能的に使用する方法を示す非常に優れた記事です。


私が推奨読書リストトニー・モリスに追加します最近発表されたチュートリアル『何をモナド意味ですか?』:projects.tmorris.net/public/what-does-monad-mean/artifacts/1.0/...
ランドール・シュルツ

3

ランドールの答えティーザーに加えて、値の潜在的な欠如が表される理由をOption理解するには、何を理解する必要がありますOption、Scalaの他の多くのタイプ、特にモナドをモデル化するタイプと共有をます。nullで値が存在しないことを表す場合、その不在と存在の区別は、他のモナドタイプによって共有されるコントラクトに参加できません。

モナドが何であるかわからない場合、またはモナドがScalaのライブラリでどのように表現されているかに気付かない場合は、何Optionが一緒に再生されるかがわかりません。また、何が欠けているかがわかりません。Optionモナドの概念がない場合でも注目に値するnullの代わりに使用することには多くの利点があります(ここでは「オプションのコスト/いくつかとnull」のscalaユーザーメーリングリストスレッドでそれらのいくつかについて説明します)が、分離は、特定のリンクリスト実装のイテレータタイプについて話し、なぜそれが必要なのか疑問に思うようなものですが、より一般的なコンテナ/イテレータ/アルゴリズムインターフェイスを見逃しています。ここでも、より幅広いインターフェースが機能しています。Option


リンクをありがとう。本当に助かりました。:)
missingfaktor

スレッドに関するあなたのコメントはとても簡潔で、私はその要点をほとんど見逃していました。私は本当にnullが禁止されることを望みます。
Alain O'Dea 2010

2

キーはSynessoの答えにあると思います。オプションは主にnullの面倒なエイリアスとしてではなく、ロジックを支援する本格的なオブジェクトとして役立ちます。

nullの問題は、オブジェクトがないことです。それを処理するのに役立つ可能性のあるメソッドはありません(ただし、言語デザイナーとして、本当に気になったらオブジェクトをエミュレートする機能のリストを言語に追加することができます)。

あなたが示したように、Optionができることの1つは、nullをエミュレートすることです。次に、異常値「null」ではなく、異常値「None」をテストする必要があります。忘れると、どちらの場合も悪いことが起こります。オプションを使用すると、「get」と入力する必要があるため、偶然に発生する可能性が低くなります(nullの可能性があることを思い出させる必要があります)が、これは追加のラッパーオブジェクトと引き換えに小さな利点です。 。

Optionが実際にその力を発揮し始めるのは、私が望んでいたものの概念に対処するのに役立っていますが、実際には持っていません。

nullの可能性があるものでやりたいことがいくつかあると考えてみましょう。

nullがある場合は、デフォルト値を設定したい場合があります。JavaとScalaを比較してみましょう。

String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"

やや面倒な?:構文の代わりに、「nullの場合はデフォルト値を使用する」というアイデアを処理するメソッドがあります。これにより、コードが少しクリーンアップされます。

本当の価値がある場合にのみ、新しいオブジェクトを作成したい場合があります。比較:

File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))

Scalaはわずかに短く、エラーの原因を回避します。次に、Synesso、Daniel、およびparadigmaticの例に示されているように、物事を連鎖させる必要がある場合の累積的な利点を検討します。

これは大きな改善ではありませんが、すべてを合計すると、非常に高性能なコードを保存するだけの価値があります(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ケースをすべてまとめて、必要な値を正確に引き出す1つの便利なステートメントにまとめることができます。


2

ヌルの戻り値は、Javaとの互換性のためにのみ存在します。それ以外の方法で使用しないでください。


1

それは本当にプログラミングスタイルの質問です。関数型Javaを使用するか、独自のヘルパーメソッドを作成することで、Option機能を使用できますが、Java言語を放棄することはできません。

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

Scalaにデフォルトで含まれているからといって、特別なものにはなりません。関数型言語のほとんどの側面はそのライブラリで利用可能であり、他のJavaコードとうまく共存できます。nullを使用してScalaをプログラムすることを選択できるのと同じように、nullを使用せずにJavaをプログラムすることを選択できます。


0

それがglibの答えであることを事前に認めて、Optionはモナドです。


私はそれがモナドであることを知っています。問題の「モナド」タグを他に含めるのはなぜですか?
missingfaktor 2010年

^上記の記述は、私がモナドが何であるかを理解しているという意味ではありません。:D
missingfaktor

4
モナドはかっこいいです。それらを使用しないか、少なくとも理解するふりをしない場合、あなたはクールではありません;-)
パラダイム

0

実際、私はあなたと疑問を共有します。オプションについては、1)パフォーマンスのオーバーヘッドがあり、毎回作成される「いくつかの」ラッパーがたくさんあるので、本当に気になります。2)コードでSomeとOptionをたくさん使用する必要があります。

したがって、この言語設計の決定の長所と短所を確認するには、代替案を考慮する必要があります。Javaはnull可能性の問題を無視するだけなので、これは代替手段ではありません。実際の代替手段はFantomプログラミング言語を提供します。そこにはnull許容型と非null許容型があります。?:Scalaのmap / flatMap / getOrElseの代わりに演算子。比較すると、次の箇条書きが表示されます。

オプションの利点:

  1. より単純な言語-追加の言語構造は必要ありません
  2. 他のモナドタイプと均一

Nullableの利点:

  1. 典型的な場合の短い構文
  2. パフォーマンスの向上(map、flatMap用に新しいOptionオブジェクトとラムダを作成する必要がないため)

したがって、ここには明らかな勝者はありません。そしてもう1つ注意してください。Optionを使用することによる主要な構文上の利点はありません。次のように定義できます。

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

または、いくつかの暗黙的な変換を使用して、ドットを使用した簡潔な構文を取得します。


最新のVMのパフォーマンスヒットについて確かなベンチマークを行った人はいますか?エスケープ分析は、多くの一時的なOptionオブジェクトをスタックに割り当てることができ(ヒープよりもはるかに安価)、世代別GCがわずかに少ない一時的なオブジェクトをかなり効率的に処理することを意味します。もちろん、NPEを回避するよりも速度がプロジェクトにとって重要である場合、オプションはおそらく適切ではありません。
ジャスティンW

それをバックアップするための数字なしでパフォーマンスのオーバーヘッドについて言及しないでください。これは、Optionのような抽象化に反対する場合に非常によくある間違いです。ベンチマークを指摘または公開したり、パフォーマンスコメントを削除したりした場合は、喜んで反対票を取り消します:)
Alain O'Dea 2010

0

明示的なオプションタイプを持つことの本当の利点は、すべての場所の98%でそれらを使用できないため、null例外を静的に排除できることです。(そして他の2%では、型システムは実際にそれらにアクセスするときに適切にチェックするようにあなたに思い出させます。)


-3

Optionが機能する別の状況は、型がnull値を持つことができない状況です。Int、Float、Doubleなどの値にnullを格納することはできませんが、Optionを使用するとNoneを使用できます。

Javaでは、これらのタイプのボックス化されたバージョン(整数、...)を使用する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.