(たとえば)null
の代わりにMaybe
を使用することの(非)便宜について読んでいます。 この記事を読んだ後、私はそれを使用する方がはるかに優れていると確信していますMaybe
(または同様のもの)。ただし、「よく知られている」命令型またはオブジェクト指向のプログラミング言語がすべてnull
(「何もない」値を表す可能性がある型への未チェックのアクセスを許可する)を使用していること、そしてMaybe
は主に関数型プログラミング言語で使用されます。
例として、次のC#コードを見てください。
void doSomething(string username)
{
// Check that username is not null
// Do something
}
何かにおいがします...引数がnullかどうかを確認する必要があるのはなぜですか?すべての変数にオブジェクトへの参照が含まれていると想定すべきではありませんか?ご覧のとおり、問題は、ほとんどすべての変数がnull参照を含む可能性があることです。どの変数が「null可能」であり、そうでないかを決定できるとしたらどうでしょうか?これにより、デバッグして「NullReferenceException」を探すときに多くの労力を節約できます。 デフォルトでは、null参照を含む型がないと想像してください。その代わりに、本当に必要な場合にのみ、変数にnull参照を含めることができることを明示的に記述します。それが多分の背後にあるアイデアです。場合によっては失敗する関数(たとえば、ゼロによる除算)がある場合、Maybe<int>
を返し、結果がintである可能性があるが何もないことを明示的に示すことができます。これはnullではなくMaybeを選択する1つの理由です。他の例に興味がある場合は、この記事を読むことをお勧めします。
事実は、ほとんどの型をデフォルトでnull可能にすることの不利な点にもかかわらず、ほとんどのOOプログラミング言語が実際にそれを行うということです。
null
の代わりにプログラミング言語でMaybe
を実装するには、どのような引数がありますか? 理由はありますかそれとも単なる「歴史的な手荷物」ですか?この質問に答える前に、nullと多分多分の違いを理解してください。
主に歴史的な手荷物だと思います。
null
を使用する最も有名で最も古い言語は、CおよびC++です。しかし、ここではnull
は理にかなっています。ポインタはまだかなり数値的で低レベルの概念です。また、CおよびC++プログラマーの考え方では、ポインターがnull
である可能性があることを明示的に指示する必要があることは意味がありません。
2行目はJavaです。 Java開発者はC++に最も近づこうとしていたため、C++からJavaへの移行を簡単にできるようになりました。おそらく、そのようなものを台無しにしたくなかったでしょう。言語のコアコンセプト。また、明示的なnull
の実装には、初期化後にnull以外の参照が実際に適切に設定されているかどうかを確認する必要があるため、はるかに多くの労力が必要になります。
他のすべての言語はJavaと同じです。彼らは通常、C++またはJavaが行う方法をコピーし、参照型の暗黙のnull
のコアコンセプトがいかに重要であるかを考えると、明示的に使用する言語を設計することは本当に困難になります。 null
。
実際、null
は素晴らしいアイデアです。ポインターが与えられたら、このポインターが有効な値を参照しないことを指定したいと思います。したがって、1つのメモリロケーションを取得し、それを無効として宣言し、その規則(segfaultで強制される場合がある規則)を遵守します。これで、ポインターがあるときはいつでも、ポインターにNothing
(_ptr == null
_)またはSome(value)
(_ptr != null
_、_value = *ptr
_)が含まれているかどうかを確認できます。これはMaybe
タイプと同等であることを理解してほしい。
これに関する問題は次のとおりです。
多くの言語では、型システムはnull以外の参照を保証するためにここでは役立ちません。
多くの主流の命令型またはOOP言語は、以前の言語と比較して型システムが段階的に進歩しているだけなので、これは歴史的な手荷物です。小さな変更には、新しい言語の学習が容易になるという利点があります。C#はnullをより適切に処理するための言語レベルのツールを導入した主流の言語。
API設計者は、失敗した場合はnull
を返しますが、成功した場合は実際のもの自体への参照を返しません。多くの場合、事物(参照なし)は直接返されます。この1つのポインターレベルの平坦化により、null
を値として使用できなくなります。
これは設計者側の単なる怠惰であり、適切な型システムで適切な入れ子を強制せずには手助けできません。一部の人々は、パフォーマンスの考慮事項、またはオプションのチェックの存在でこれを正当化しようとする場合もあります(コレクションはnull
またはアイテム自体を返す可能性がありますが、contains
メソッドも提供します)。
Haskellには、モナドとしてのMaybe
型についてのきちんとした見方があります。これにより、含まれている値の変換を簡単に作成できます。
一方、Cのような低水準言語は、配列を別個の型としてほとんど扱いません。そのため、私たちが何を期待しているのかわかりません。 OOPパラメータ化されたポリモーフィズムを持つ言語では、実行時にチェックされるMaybe
型を実装するのは簡単です。
私の理解では、null
は、アセンブリからプログラミング言語を抽象化するために必要な構造でした。1 プログラマーは、ポインターまたはレジスターの値がnot a valid value
であり、null
がその意味の一般的な用語になったことを示す機能が必要でした。
null
は概念を表すための単なる慣例であるという点を強調します。null
の実際の値は、プログラミング言語とプラットフォームに基づいて/できる場合があります。
新しい言語を設計していて、null
を避けたいが、代わりにmaybe
を使用したい場合は、not a valid value
やnavv
などのよりわかりやすい用語を使用して、意図を示します。しかし、その非値のnameは、allowあなたの言語にも存在する非値。
これらの2つの点のいずれかを決定する前に、maybe
の意味がシステムにとってどのような意味を持つかを定義する必要があります。 null
のnot a valid value
の意味の単なる名前の変更である場合や、言語によって意味が異なる場合があります。
同様に、null
アクセスまたは参照に対してアクセスをチェックするかどうかの決定は、言語の別の設計上の決定です。
少し歴史を提供するために、C
には、プログラマーがメモリーを操作するときに何をしようとしているかを理解しているという暗黙の仮定がありました。それはアセンブリとそれに先行する命令型言語よりも優れた抽象概念であったため、プログラマを誤った参照から保護するという考えが彼らの頭に浮かばなかったと思います。
一部のコンパイラまたはそれらの追加のツールは、無効なポインタアクセスをチェックする手段を提供できると思います。そのため、他の人たちはこの潜在的な問題を指摘し、それを防ぐための対策を講じています。
許可する必要があるかどうかは、言語で達成したいことと、言語のユーザーにプッシュしたい責任の程度によって異なります。また、そのタイプの動作を制限するコンパイラーを作成する能力にも依存します。
だからあなたの質問に答えるには:
「どのような議論...」-まあ、それはあなたが言語に何をさせたいかに依存します。ベアメタルアクセスをシミュレートする場合は、許可することができます。
「単なる歴史的な手荷物なのか?」おそらく、そうではないでしょう。 null
は確かに多くの言語にとって意味があり、それらの言語の表現を促進するのに役立ちます。歴史的な先例は、より最近の言語とnull
の許可に影響を与えた可能性がありますが、手を振ってコンセプトを役に立たない歴史的な手荷物と宣言するのは少し大変です。
1このWikipediaの記事を参照してください ただし、ヌル値とオブジェクト指向言語についてはHoareに感謝しています。命令型言語は、ALGOLとは異なる家系図に沿って進歩したと思います。
引用した記事の例を見ると、ほとんどの場合、Maybe
を使用してもコードは短くなりません。 Nothing
を確認する必要はありません。唯一の違いは、型システムを介してそうするように促すことです。
注:強制ではなく、「思い出させる」と言います。プログラマーは怠惰です。プログラマーが値がNothing
である可能性がないと確信している場合、彼らはチェックをせずにMaybe
を逆参照します。これは、まるでnullポインターを逆参照するのと同じです。最終結果は、nullポインタ例外を「逆参照された空の多分」例外に変換することです。
人間の性質の同じ原理は、プログラミング言語がプログラマーに何かを強制しようとする他の領域にも適用されます。たとえば、Java設計者は、ほとんどの例外を処理するようユーザーに強制しようとしました。その結果、例外を黙って無視するか、または盲目的に例外を伝播する定型文がたくさんありました。
Maybe
がいいのは、明示的なチェックではなく、パターンマッチングとポリモーフィズムによって多くの決定が行われるときです。たとえば、null
では実行できない別個の関数processData(Some<T>)
とprocessData(Nothing<T>)
を作成できます。エラー処理を自動的に別の関数に移動します。これは、関数が常にトップダウン方式で呼び出されるのではなく、関数が渡されて遅延評価される関数型プログラミングで非常に望ましいものです。 OOPでは、エラー処理コードを分離するための推奨される方法は例外です。
null
の概念は簡単にCまでさかのぼることができますが、問題はそこにはありません。
私の日常の選択言語はC#で、null
は1つだけ違います。 C#には、valuesとreferencesの2種類のタイプがあります。値をnull
にすることはできませんが、完全に適切な値がないことを表現したい場合があります。これを行うには、C#はNullable
型を使用するため、int
が値であり、int?
null許容値。これは、参照型も同様に機能するはずだと私が思う方法です。
また参照してください: null参照は間違いではない可能性があります :
Null参照は有用であり、時には不可欠です(C++で文字列を返す場合と返さない場合の問題を考慮してください)。間違いは、実際にはnullポインターの存在ではなく、型システムによるNULLポインターの処理にあります。残念ながら、ほとんどの言語(C++、Java、C#)では正しく処理されません。
Maybe
は、問題を考える非常に機能的な方法です。物事があり、定義された値がある場合とない場合があります。ただし、オブジェクト指向の意味では、モノの考え方(値があるかどうかに関係なく)をオブジェクトに置き換えます。明らかに、オブジェクトには値があります。そうでない場合、オブジェクトはnull
であると言いますが、実際には、オブジェクトがまったくないということです。オブジェクトへの参照が何も指していない。 Maybe
をOOの概念に変換しても、目新しいことは何もありません。実際、コードが雑然としているためです。値のnull参照が必要です。 Maybe<T>
。 「多分チェック」と呼ばれるようになったとしても、nullチェックを実行する必要があります(実際には、コードを乱雑にして、さらに多くのnullチェックを実行する必要があります)。もちろん、作成者が主張するように、より堅牢なコードを記述しますが、それは、言語をはるかに抽象的で鈍くし、ほとんどの場合不必要なレベルの作業を必要とするためです。新しい変数にアクセスするたびにMaybeチェックを実行するスパゲッティコードを処理するよりも、NullReferenceExceptionをたまに取りたいと思います。
基本的な理由は、プログラムをデータの破損に対して「安全」にするために必要なnullチェックが比較的少ないことです。プログラムが、有効な参照で記述されているはずの配列要素またはその他の格納場所の内容を使用しようとしたが、そうではなかった場合、最良の結果は、例外がスローされることです。理想的には、例外は問題が発生した場所を正確に示しますが、重要なのは、データ破損を引き起こす可能性がある場所にnull参照が格納される前に、ある種の例外がスローされることです。メソッドが何らかの方法で最初にオブジェクトを使用しようとせずにオブジェクトを格納しない限り、オブジェクトを使用しようとすると、それ自体でソートの「nullチェック」が構成されます。
Null参照が表示されるべきではない場所にあると、NullReferenceException
以外の特定の例外が発生することを確認したい場合は、多くの場合、場所全体にnullチェックを含める必要があります。一方、null参照がすでに行われたものを超えて「損傷」を引き起こす前に、いくつかの例外が発生することを確認するだけでは、多くの場合、比較的少数のテストが必要になります。テストは、通常、オブジェクトがand null参照は有効な参照を上書きしますor他のコードがプログラムの状態の他の側面を誤って解釈する原因になります。このような状況は存在しますが、それほど一般的ではありません。ほとんどの偶発的なnull参照は非常に迅速にキャッチされますチェックするかどうか。
「Maybe
」は、記述されているように、nullよりも高レベルの構成です。それを定義するためにより多くの単語を使用して、多分「ものへのポインタ、または何へのポインタでもないが、コンパイラはまだどれを決定するのに十分な情報が与えられていません。」これにより、作成するコードに追いつくのに十分に賢いコンパイラー仕様を作成しない限り、各値を常に明示的にチェックする必要があります。
多分nullを持つ言語でMaybeを実装できます。 C++には、boost::optional<T>
という形式のものが1つあります。 Maybeでnullと同等のものを作成することは非常に困難です。特に、Maybe<Just<T>>
がある場合、それをnullに割り当てることはできません(そのような概念が存在しないため)。一方、nullを含む言語のT**
は、nullに割り当てるのが非常に簡単です。これにより、完全に有効なMaybe<Maybe<T>>
を使用するように強制されますが、そのオブジェクトを使用するには、さらに多くのチェックを実行する必要があります。
Nullは未定義の動作または例外処理のいずれかを必要とするため、一部の関数型言語は多分多分使用しますが、どちらも関数型言語構文にマップする簡単な概念ではありません。このような関数型の状況では、この役割がはるかによく満たされるかもしれませんが、手続き型言語では、nullが最も重要です。それは正誤の問題ではなく、何をしたいのかをコンピュータに指示するのを容易にするものの問題です。
これは、関数型プログラミングがtypesを特に懸念しているためだと思います。特に、オブジェクト指向プログラミングよりも他の型(タプル、ファーストクラスの型としての関数、モナドなど)を組み合わせる型(または少なくとも当初は)。
あなたが話していると思うプログラミング言語の最新バージョン(C++、C#、Java)はすべて、一般的なプログラミングの形式を持たない言語(C、C#1.0、Java = 1)それがなくても、ヌル可能オブジェクトとヌル可能でないオブジェクトの間に何らかの種類の違いを言語に焼き付けることができます(null
にすることはできませんが、制限もあるC++参照のように)、しかしそれははるかに自然ではありません。