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 はコードをどのように記述すべきかを教えてくれません。それはあなたを穏やかに刺激するかもしれませんが、セーフティネットのないところに戻りたい場合は、それはあなたの選択です。
ここでうまくいきました:
オプションの唯一の利点は、この方法が何も返さないことをプログラマーに明示的に伝えていることです。
「だけ」を除いて。しかし、これを別の言い方で言い直してみましょう。の 主要 の利点 Option[T]
以上 T
タイプセーフティです。これにより、 T
コンパイラでは許可されないため、存在しない可能性のあるオブジェクトにメソッドを追加します。
どちらの場合も null 可能性をテストする必要があると言いましたが、忘れた場合、または知らない場合、null をチェックする必要があるとコンパイラは指示しますか?それともユーザーがそうするでしょうか?
もちろん、Java との相互運用性により、Scala は Java と同様に null を許可します。Java ライブラリを使用する場合、不適切に書かれた Scala ライブラリを使用する場合、または不適切に書かれたライブラリを使用する場合 個人的 Scala ライブラリでは、引き続き null ポインターを処理する必要があります。
その他の 2 つの重要な利点 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で表示され、彼らは「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)
私は、このオプションは、生活が少し楽に行うことができますどのようにいくつかの光を当てる願っています。
の違いは微妙です。 // EN:nullが本当にその意味で、「通常の戻り値」であるとは考えられない、より<のhref = "HTTP - 本当に機能それの必要がありますの値を返すことに注意してください.wikipedia.org /ウィキ/ Bottom_type」のrel = "nofollowをnoreferrer">ボトム型を/何もありません。
しかし、あなたは、必要に応じて何かを返す関数を呼び出すときに実用的な意味では、あなたがどうなるます:
getPerson2 match {
case Some(person) => //handle a person
case None => //handle nothing
}
確かに、あなたはヌルと似た何かを行うことができます - しかし、これはドキュメントを読んで誰かに頼ると、彼らので、NPEを得る以外のgetPerson2
(素敵な実用的なものを、返すという事実のおかげで呼び出す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渡すことができます。あなたはnullをチェックしないと、あなたは例外を気にしません。 のsynesso の例で提示Javaのバージョンにこれを比較します。
あなたはオプションで、かなり強力な構図能力を持っています:
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") )
たぶん他の誰かがこのことを指摘したが、私はそれを見ていない。
オプションあなたがいずれかの一部またはNoneケースをコーディングするのを怠るとScalaのコンパイラが警告を発行しますので、[T]対ヌルチェックは、そのオプションが密閉されたクラスであるとのパターンマッチングの利点の一つ。警告をエラーになりますコンパイラにコンパイラフラグがあります。だから、コンパイル時ではなく、実行時に「存在しない」場合を処理するための故障を防止することが可能です。これは、NULL値を使用する上で非常に大きな利点がある。
これは、それがnullチェックを強制的にあります、nullのチェックを避けるためにありません。あなたのクラスは10の分野、nullになる可能性がありそのうちの2つを持っていたときにポイントが明確になります。そして、あなたのシステムが50の他の類似のクラスがあります。 Javaの世界では、あなたは精神的horesepowerのいくつかの組み合わせを使用してこれらのフィールド上のNPE、命名規則、または多分注釈を防ぐためにしてみてください。そして、すべてのJava devがかなりの程度まで、この時に失敗しました。オプションクラスは、コードを理解しようとするすべての開発者に視覚的に明確な「NULL可能」の値になりますが、コンパイラはこれ以前に暗黙の契約を強制することを可能にするだけではなく。
[からコピーしました このコメント による ダニエル・スピーワク ]
唯一の使い方なら
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
-内包表記: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開発者を知っています。デビッド・ポラックは先日、彼がそのような明示的なマッチングを使用していると言ったOption
(またはBox
, 、リフトの場合)コードを書いた開発者が言語とその標準ライブラリを完全に理解していないことのサインとして。私はトロールハンマーになるつもりはありませんが、あなたがそれらを役に立たないと非難する前に、実際に実際に *実際に *実際に使用されている方法を見る必要があります。私は *あなたがそれを使用したので、オプションは非常に驚くことであることに絶対に同意しますが、あなたはそれが設計された方法でそれを使用していません。
誰もがここで提起しているように思わないことが一つのポイントは、あなたがnull参照を持つことができますが、オプションで導入された区別があるということです。
それはあなたがOption[Option[A]]
がNone
の通常の住民の一つであるSome(None)
、Some(Some(a))
とa
が居住されるだろうA
を持つことができます。これは、コンテナのいくつかの種類があり、それにはnullポインタを格納し、それらを得ることができるようにしたい場合は、あなたが実際に値を外に出たかどうかを知るために、いくつかの余分なブール値を返すため必要があることを意味します。このようないぼは、ののJavaのコンテナのAPIと、いくつかのロックフリーの変種でも、それらを提供することができないたくさんあります。
null
は、それが参照型でのみ使用でき、それは自分自身で構成していない、一回限りの構築であり、それはあなたが非トータルファッションに理由に強制します。
たとえば、あなたがチェックしたときに、
if (x == null) ...
else x.foo()
あなたはelse
x != null
支店を通じて、あなたの頭の中で、これはすでに確認されていることを持ち歩く必要があります。しかし、オプションのようなものを使用している場合
x match {
case None => ...
case Some(y) => y.foo
}
は、<全角> のyが建設をNone
byされていません知っている - そしてそれはホーアの<のhref = "HTTPのためではなかった場合は、それはどちらかnull
いませんでした知っているだろう://ラムダ-ultimate.org/node/3186">billionドルの間違いでます。
Option[T] はモナドであり、高階関数を使用して値を操作する場合に非常に便利です。
以下にリストされている記事を読むことをお勧めします。これらの記事は、Option[T] がなぜ便利なのか、また機能的にどのように使用できるのかを説明する非常に優れた記事です。
の値がなぜ可能性がないことを理解し、解答するのランドールのティーザーへに追加Option
で表されるスカラ座特異的で、他の多くの種類、モナドをモデリングタイプの理解何Option
株式を必要とします。一方がヌルで値が存在しないことを表す場合、その不在存在の区別は、他のモナドタイプによって共有契約に参加することはできません。
あなたはモナドが何であるかを知らない、またはあなたがそれらはScalaのライブラリに表現しているか気づかない場合、あなたは一緒に演じて何Option
表示されませんし、あなたがしているものを見ることができない場合逃し。でも、任意のモナドの概念が存在しない場合には注目すべきだろうOption
代わりにヌルを使用することには多くの利点があります(私はのScalaのユーザーのメーリングリスト「オプション/いくつかのヌル対のコスト」でそれらのいくつかを議論リストここのスレッド)が、の話その分離は一種のすべての中には、より一般的なコンテナ/イテレータ/アルゴリズムインターフェイスを逃し、それは必要だ理由を不思議、特定のリンクリストの実装のイテレータ型の話のようなものです。そこここに仕事でより広範なインタフェースは、あまりにもだし、Option
は、そのインターフェイスのプレゼンスと不在のモデルを提供しています。
私は鍵がSynessoの答えで見ていると思います。オプションは、のないのヌルのための面倒なエイリアスとして主に有用ですが、その後、あなたのロジックであなたを助けることができる本格的なオブジェクトとして<。 / P>
ヌルの問題点は、オブジェクトのの不足のであるということです。 (言語設計者として、あなたは本当にそれのように感じる場合は、オブジェクトをエミュレートするあなたの言語への機能のますます長いリストを追加することができますが)それはあなたがそれに対処するに役立つかもしれないメソッドはありません。
あなたが実証されてきたようにオプションが行うことができます。一つは、ヌルをエミュレートすることです。あなたはその後、臨時値「なし」の代わりに、特別な値「ヌル」のためにテストする必要があります。あなたが忘れてしまった場合、いずれの場合も、悪い事が起こります。オプションでは、「取得」と入力する必要があるため(それはののヌルであるかもしれないことを思い出させるべきである、えー、私はなしを意味しない)、事故によって起こり、それは可能性が低い作りませんが、これは、小さな利益であります余分なラッパーオブジェクトと引き換えにます。
オプションは、本当にあなたがI-たかった - 何か - しかし-I-、ドント実際に持っている-1の概念に対処する助けているその力を示し始めるところます。
あなたがヌルであるかもしれないものにしたいと思うかもしれないいくつかのことを考えてみましょう。
たぶん、あなたは、あなたが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、ダニエル、そしてパラダイムでの例に示すように、累積利益を考慮します。
これはの広大なの改善はありませんが、あなたはすべてを追加した場合、それはどこでもあなたには、いくつかの作成にも小さなオーバーヘッドを避けたい非常に高性能なコード(保存、それも価値があります(x)は、ラッパー・オブジェクト)。
試合の利用がnull /なしケースについてユーザーに警告するためにデバイスとして除いて自分自身で本当に便利ではありません。それは本当に便利である場合は、あなたがそれを連鎖起動時にオプションのリストを持っている場合、例えば、次のとおりです。
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.")
}
さて、あなたはなしケースを折るために取得し、一覧には、ある空の場合、すべて一緒にしたい正確に価値を引き出す1つの重宝文でます。
NULLの戻り値は、Javaとの互換性のためだけに存在しています。あなたは、そうでない場合は、それらを使用しないでください。
これは実際にプログラミングスタイルの問題です。機能のJavaを使用して、または独自のヘルパーメソッドを書くことによって、あなたのオプション機能を持ってではなく、Java言語を放棄することができます:
http://functionaljava.org/examples/#Option.bindする
Scalaは、デフォルトでは、それを含んでいるからといって、それは特別なことはありません。関数型言語のほとんどの側面は、そのライブラリで提供され、それが他のJavaコードとうまく共存させることができます。あなたがNULLでScalaのをプログラムするかを選択できるのと同様に、あなたがそれらなしのJavaをプログラミングすることを選択できます。
それは口が達者な答えであることを事前に認める、オプションモナドです。
実は私もあなたとその疑問を共有しています。オプションについては、1) あちこちで作成された「いくつかの」ラッパーがあるため、パフォーマンスのオーバーヘッドがあることが本当に気になります。2) コード内で Some と Option を大量に使用する必要があります。
したがって、この言語設計の決定の長所と短所を確認するには、代替案を考慮する必要があります。Java は null 可能性の問題を無視するだけなので、代替手段にはなりません。実際の代替手段は、Fantom プログラミング言語を提供します。そこには null 許容型と null 非許容型があります。?:Scala の map/ flatMap/getOrElse の代わりに演算子を使用します。比較すると次のような箇条書きが表示されます。
オプションの利点:
- より単純な言語 - 追加の言語構造は必要ありません
- 他のモナド型と均一
Nullable の利点:
- 一般的な場合の短い構文
- パフォーマンスの向上 (マップ、フラットマップ用に新しい Option オブジェクトとラムダを作成する必要がないため)
したがって、ここには明らかな勝者はいません。そしてもう一つ注意事項。Option を使用することによる主な構文上の利点はありません。次のように定義できます。
def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)
または、暗黙的な変換を使用して、ドットを使用したきれいな構文を取得します。
明示的なオプションの種類を持つことの本当の利点は、あなたがのないのすべての場所の98%にそれらを使用するため、静的にヌル例外を排除することができることです。 (そして、他の2%に型システムは、あなたが実際にそれらにアクセスする際、適切にチェックするためにあなたを思い出させます。)
オプションが機能する別の状況は、型がnull値を持つことができない状況にあります。これは、INT、フロート、ダブル、など値にnullを保存することはできませんが、オプションで[なし]を使用することはできません。
Javaでは、あなたは、これらのタイプの箱入りのバージョン(整数、...)を使用する必要があります。