ScalaのOption[T]
クラスのポイントが理解できません。つまり、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は、Javaで使用される単純なアプローチに従う代わりに、新しい値ラッパー(Option[T]
)を導入することによって物事を複雑にするのですか?
更新:
@ Mitch の提案に従ってコードを編集しました。 Option[T]
の特別な利点はまだわかりません。どちらの場合も、例外的なnull
またはNone
をテストする必要があります。 :(
@ Michaelの返信 から正しく理解した場合、Option[T]
の唯一の利点は、プログラマーにこのメソッドはNoneを返す可能性がある?これがこのデザインの選択の背後にある唯一の理由ですか?
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を返す可能性があることをプログラマーに明示的に伝えることです。
「のみ」を除いて。しかし、別の言い方をすれば、mainT
に対するOption[T]
の利点は型安全性です。コンパイラが許可しないため、存在しない可能性のあるオブジェクトにT
メソッドを送信しないようにします。
どちらの場合もnull可能性をテストする必要があるとおっしゃいましたが、nullを忘れた場合、またはわからない場合は、nullをチェックする必要があります。コンパイラは教えてくれますか?それともユーザーですか?
もちろん、Javaとの相互運用性のため、Scalaは、Javaと同じように、nullを許可します。したがって、Javaライブラリ、不適切に記述されたScalaライブラリを使用する場合、または不適切に記述されたライブラリを使用する場合personal Scalaライブラリ) nullポインタを処理します。
Option
の他の2つの重要な利点は次のとおりです。
ドキュメント:メソッドタイプのシグネチャは、オブジェクトが常に返されるかどうかを示します。
モナドの構成可能性。
後者は完全に理解するのにはるかに長い時間がかかり、複雑なコードでのみその強みを示すため、単純な例にはあまり適していません。それで、以下に例を挙げましょうが、それはすでにそれを手に入れている人々以外にはほとんど何の意味もないことを私はよく知っています。
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 as 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)
これにより、オプションによって生活が少し楽になる方法が明らかになることを願っています。
違いは微妙です。本当に関数であることに注意してくださいmustは値を返します-nullは、その意味で「通常の戻り値」とは実際には見なされません。 a ボトムタイプ /なし。
ただし、実際的な意味では、オプションで何かを返す関数を呼び出すと、次のようになります。
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
割り当てのいずれかがNone
の場合、fathersMothersSister
はNone
になりますが、NullPointerException
は発生しません。その後、心配することなく、Optionパラメータを受け取る関数にfathersMothersSister
を安全に渡すことができます。したがって、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") )
他の誰かがこれを指摘したかもしれませんが、私はそれを見ませんでした:
Option [T]とnullチェックを使用したパターンマッチングの利点の1つは、Optionが封印されたクラスであるため、SomeまたはSomeのコーディングを怠ると、Scalaコンパイラーが警告を発行することです。ケースなし。警告をエラーに変換するコンパイラフラグがコンパイラにあります。したがって、実行時ではなくコンパイル時に「存在しない」ケースの処理の失敗を防ぐことができます。これは、 null値の使用。
ヌルチェックを回避するためにあるのではなく、ヌルチェックを強制するためにあります。クラスに10個のフィールドがあり、そのうち2個がnullになる可能性がある場合、ポイントが明確になります。そして、あなたのシステムには他に50の同様のクラスがあります。 Javaの世界では、精神的な力、命名規則、あるいは注釈の組み合わせを使用して、これらのフィールドでNPEを防止しようとします。そしてすべてのJava dev Optionクラスは、コードを理解しようとしている開発者に「null可能な」値を視覚的に明確にするだけでなく、コンパイラーがこの以前は口に出さなかった契約を実施できるようにします。
ここで他の誰も提起していないように思われる1つのポイントは、null参照を持つことはできますが、Optionによって導入された違いがあるということです。
つまり、None
、Some(None)
、およびSome(Some(a))
が存在するOption[Option[A]]
を持つことができます。ここで、a
は通常の1つです。 A
の住民。つまり、ある種のコンテナがあり、その中にnullポインタを格納してそれらを取得できるようにしたい場合は、実際に値を取得したかどうかを知るために、追加のブール値を返す必要があります。 JavaコンテナAPIおよび一部のロックフリーバリアントでは、このようなaboundのような疣贅はそれらを提供することさえできません。
null
は1回限りの構造であり、それ自体で構成されることはなく、参照型でのみ使用可能であり、合計ではない方法で推論する必要があります。
たとえば、チェックするとき
if (x == null) ...
else x.foo()
x != null
でありこれがすでにチェックされているelse
ブランチ全体で頭の中で持ち歩く必要があります。ただし、オプションのようなものを使用する場合
x match {
case None => ...
case Some(y) => y.foo
}
youknowyは構造上None
ではありません-そして、もしあなたはそれがnull
でもなかったことを知っているでしょうHoareの 10億ドルの間違い ではありませんでした。
[ このコメント によって Daniel Spiewak からコピー]
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開発者を知っています。DavidPollakが先日私に話しました。彼は、コードを書いた開発者が言語とその標準ライブラリを完全に理解していないことを示す兆候として、Option
(またはLiftの場合はBox
)でそのような明示的なマッチングを使用していること。私はトロールハンマーになるつもりはありませんが、言語機能が役に立たないものとしてバッシングする前に、実際にどのように使用されているかを実際に確認する必要があります。 Optionは、*あなた*が使用したので非常に説得力がないことに完全に同意しますが、設計どおりに使用していません。
Randallの 答えのティーザー に加えて、値の潜在的な欠如がOption
で表される理由を理解するには、Option
がScalaの他の多くのタイプと共有するものを理解する必要があります—具体的には、モナドをモデル化するタイプ。 nullで値が存在しないことを表す場合、その不在と存在の区別は、他のモナドタイプによって共有されるコントラクトに参加できません。
モナドが何であるかわからない場合、またはモナドがScalaのライブラリでどのように表現されているかに気付かない場合は、Option
が何と一緒に再生されるかがわかりません。また、モナドが何であるかがわかりません。を逃しています。 nullの代わりにOption
を使用することには多くの利点があり、モナドの概念がない場合でも注目に値します(「オプションのコスト/いくつかとnull」でそれらのいくつかについて説明しますscala-userメーリングリストスレッド ここ )しかし、それについて話すことは、特定のリンクリスト実装のイテレータタイプについて話すようなもので、なぜそれが必要なことですが、より一般的なコンテナ/イテレータ/アルゴリズムのインターフェイスを見逃しています。ここでもより広範なインターフェースが機能しており、Option
はそのインターフェースの存在と不在のモデルを提供します。
Option [T]はモナドであり、高階関数を使用して値を操作するときに非常に便利です。
以下にリストされている記事を読むことをお勧めします。これらは、Option [T]が有用である理由と、それを機能的に使用する方法を示す非常に優れた記事です。
ヌルの戻り値は、Javaとの互換性のためにのみ存在します。それ以外の方法で使用しないでください。
キーはSynessoの答えにあると思います:オプションはnot主にnullの面倒なエイリアスとして役立ちますが、ロジックを支援する本格的なオブジェクトとして役立ちます。
Nullの問題は、それがオブジェクトのlackであるということです。それを処理するのに役立つ可能性のあるメソッドはありません(ただし、言語デザイナーとして、本当に気になったらオブジェクトをエミュレートする機能のリストを言語に追加することができます)。
あなたが示したように、Optionができることの1つは、nullをエミュレートすることです。次に、異常値「null」ではなく、異常値「None」をテストする必要があります。どちらの場合も、忘れると悪いことが起こります。オプションを使用すると、「get」と入力する必要があるため、偶然に発生する可能性が低くなります(これにより、可能性があります null、えー、つまりNoneを意味します)が、これは小さな利点です。追加のラッパーオブジェクトと引き換えに。
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の例に示されているように、物事を連鎖させる必要がある場合の累積的な利点を検討します。
vastの改善ではありませんが、すべてを合計すると、非常に高性能なコードを保存するだけの価値があります(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つの便利なステートメントにまとめることができます。
それは本当にプログラミングスタイルの質問です。関数型Javaを使用するか、独自のヘルパーメソッドを作成することで、Option機能を使用できますが、Java言語:
http://functionaljava.org/examples/#Option.bind
Scalaにデフォルトで含まれているからといって、特別なものにはなりません。関数型言語のほとんどの側面がそのライブラリで利用可能であり、他のJavaコード。nullを使用してScalaをプログラムすることを選択できるのと同じように、nullを使用せずにJavaをプログラムすることを選択できます。
実際、私はあなたと疑問を共有します。オプションについては、1)パフォーマンスのオーバーヘッドがあり、毎回作成される「いくつかの」ラッパーがたくさんあるので、本当に気になります。 2)コードでSomeとOptionをたくさん使用する必要があります。
したがって、この言語設計の決定の長所と短所を確認するには、代替案を考慮する必要があります。 Javaはnull許容性の問題を無視するだけなので、代替手段ではありません。実際の代替手段はFantomプログラミング言語を提供します。そこにはnull許容型と非null許容型があり、Scalaのマップの代わりに?。?:演算子があります。/flatMap/getOrElse。比較すると次の箇条書きが表示されます。
オプションの利点:
Nullableの利点:
したがって、ここには明らかな勝者はありません。そしてもう1つ注意してください。 Optionを使用することによる主要な構文上の利点はありません。次のように定義できます。
def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)
または、いくつかの暗黙的な変換を使用して、ドットを使用した簡潔な構文を取得します。
明示的なオプションタイプを持つことの本当の利点は、すべての場所の98%でそれらを使用できることですnotしたがって、null例外を静的に排除します。 (そして他の2%では、型システムは実際にアクセスするときに適切にチェックするように促します。)
それがglibの答えであることを事前に認めて、Optionはモナドです。