ほとんどの「よく知られた」命令型/オブジェクト指向言語が、「何もない」値を表すことができる型への未チェックのアクセスを許可するのはなぜですか?


29

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可能にするという欠点にもかかわらず、ほとんどのオブジェクト指向プログラミング言語は実際にそれをNULL可能にします。それが私が疑問に思う理由です:

  • nullプログラミング言語ではなく、どのような引数を実装する必要がありMaybeますか?理由はありますか、それとも単に「歴史的な荷物」ですか?

この質問に答える前に、nullとMaybeの違いを理解してください。


3
Tony Hoareについて、特に彼の10億ドルの間違いについて読むことをお勧めします。
オデッド

2
それが彼の理由です。そして、残念な結果は、今日まで続いていたほとんどの言語にコピーされた間違いだったということです。あります言語nullの概念が存在しないかは(IIRC Haskellはその一例です)。
オデッド

9
歴史的な荷物は過小評価するものではありません。そして、これらが構築するOS nullは、長い間s を持っている言語で書かれていたことを思い出してください。それを落とすのは簡単ではありません。
オデッド

3
リンクを投稿するのではなく、Maybeの概念に光を当てるべきだと思います。
ジェフ

1
@ GlenH7 C#の文字列は参照値です(nullの場合があります)。intがプリミティブな値であることは知っていますが、多分を使用することを示すのは有用でした。
アオガガビア

回答:


15

主に歴史的な荷物だと思います。

最も有名で最も古い言語nullはCとC ++です。しかし、これnullは理にかなっています。ポインターは、まだかなり数値的で低レベルの概念です。そして、CおよびC ++プログラマーの考え方の中で他の誰かがどのように言ったかは、ポインターができることを明示的に伝える必要はありnullません。

2番目の行はJavaです。Java開発者がC ++に最も近づこうとしているので、C ++からJavaへの移行をより簡単にできると考えているため、おそらくこのような言語のコアコンセプトを台無しにしたくないでしょう。また、null初期化後にnull以外の参照が実際に適切に設定されているかどうかを確認する必要があるため、明示的な実装にはさらに多くの労力が必要になります。

他のすべての言語はJavaと同じです。彼らは通常、C ++またはJavaが行う方法をコピーします。またnull、参照型の暗黙のコアコンセプトがいかに重要であるかを考えると、explicitを使用する言語を設計することは非常に難しくなりますnull


「彼らはおそらく、このような言語のコア概念を台無しにしたくなかったでしょう」彼らはすでにポインターを完全に削除しましたが、削除してもnullそれほど大きな変更にはならないでしょう。
svick

2
@svick Javaの場合、参照はポインターの代わりになります。また、多くの場合、C ++のポインターはJava参照と同じ方法で使用されていました。一部の人々もJavaがポインタ(持っていないことを主張programmers.stackexchange.com/questions/207196/...
陶酔

これを正解としてマークしました。賛成したいのですが、評判が十分ではありません。
アオガガビア

1
同意する。C ++では(JavaやCとは異なり)、null可能であることは例外です。std::stringすることはできませんnullint&することはできませんnullint*Cがやったと2.ので、あなたがC ++での生のポインタを使用しているとき、あなたがやっているかを理解することになっているので、1:缶、およびC ++は、2つの理由のためにそれに未チェックのアクセスを可能にします。
MSalters

@MSalters:タイプにビットコピー可能なデフォルト値がない場合、そのタイプの配列を作成するには、配列自体へのアクセスを許可する前に、そのすべての要素に対してコンストラクターを呼び出す必要があります。これは無用な作業を必要とする場合があります(読み込まれる前に一部またはすべての要素が上書きされる場合)、以前の要素が構築された後に後の要素のコンストラクターが失敗した場合に複雑さを導入し、とにかく実際にはあまり達成されない可能性があります(if一部の配列要素の適切な値は、他の要素を読み取らないと決定できませんでした)。
supercat

15

実際、null素晴らしいアイデアです。ポインターが与えられた場合、このポインターが有効な値を参照しないことを指定します。そのため、1つのメモリ位置を取得し、それを無効であると宣言し、その規則に準拠します(規則はセグメンテーション違反で強制される場合があります)。これで、ポインターがあるときはいつでもNothingptr == null)またはSome(value)ptr != nullvalue = *ptr)が含まれているかどうかを確認できます。これはMaybe型と同等であることを理解してほしい。

これに関する問題は次のとおりです。

  1. 多くの言語では、型システムは非null参照を保証するためにここでは役立ちません。

    これは歴史的な手荷物です。多くの主流の命令型言語またはOOP言語は、先行システムと比較して型システムが少しずつ進歩しているだけです。小さな変更には、新しい言語を習得しやすいという利点があります。C#は、ヌルをより適切に処理するための言語レベルのツールを導入した主流言語です。

  2. APIデザイナーはnull失敗した場合に戻りますが、成功した場合には実際の事柄自体への参照ではありません。多くの場合、物(参照なし)が直接返されます。1つのポインターレベルをこのようにフラット化するnullと、値として使用できなくなります。

    これは設計者側の怠であり、適切な型システムで適切なネストを強制しないと仕方がありません。一部の人々は、パフォーマンスを考慮したり、オプションのチェックが存在することでこれを正当化しようとするかもしれません(コレクションが返されるnullか、アイテム自体が返されるかもしれませんが、containsメソッドを提供します)。

  3. HaskellにはMaybe、モナドとしての型に対するきちんとしたビューがあります。これにより、含まれる値の変換を簡単に構成できます。

    一方、Cのような低レベル言語では、配列を別個の型として扱うことはほとんどないため、何を期待しているのかわかりません。パラメータ化された多態性を持つOOP言語では、実行時チェックMaybe型の実装はかなり簡単です。


「C#はnull参照から一歩離れています。」それらはどのような手順ですか?言語の変更でそのようなものを見たことはありません。それとも、一般的なライブラリがnull過去に使用したよりも少ないものを使用しているという意味ですか?
svick

@svick悪い言い回し。「C#はnulls をより適切に処理するための言語レベルのツールを導入した主流の言語です」– Nullable型と??デフォルトの演算子について話している これは、レガシーコードの存在で、今問題を解決していませんが、それはあるより良い未来への第一歩。
アモン

私はあなたに同意します。私はあなたの答えに投票しますが、私は十分な評判がありません:(ただし、Nullableはプリミティブ型に対してのみ機能します。したがって、ほんの少しのステップです。
aochagavia

1
@svick Nullableはこれとは何の関係もありません。プログラマに明示的に定義させるのではなく、暗黙的にnull値を許可するすべての参照型について話しています。また、Nullableは値型でのみ使用できます。
陶酔

@Euphoricあなたのコメントはamonへの返信であると思いますNullable
svick

9

私の理解では、それnullはアセンブリからプログラミング言語を抽象化するために必要な構成でした。1 プログラマーは、ポインターまたはレジスター値がその意味の一般的な用語であり、現在の用語になったことを示す機能を必要not a valid valueとしていnullました。

null概念を表現するための単なる慣習である点を補強するためにnull使用されるために使用される実際の値は、プログラミング言語とプラットフォームに基づいて異なる場合があります。

新しい言語を設計していて、代わりにnull使用することを避けたい場合は、またはのmaybeようなより説明的な用語を使用することをお勧めします。しかし、その非価値の名前は、あなたの言語に非価値が存在することさえ許すべきかどうかとは別の概念です。not a valid valuenavv

これらの2つのポイントのいずれかを決定する前maybeに、システムの意味が何を意味するかを定義する必要があります。あなたはそれがのnull意味の名前を変更しただけnot a valid valueだと思うかもしれませんし、あなたの言語にとって異なる意味を持っているかもしれません。

同様に、nullアクセスまたは参照に対してアクセスをチェックするかどうかの決定は、言語の別の設計上の決定です。

少しの歴史を提供するために、Cメモリを操作するときにプログラマーが何をしようとしているかをプログラマーが理解しているという暗黙の仮定がありました。それはアセンブリとそれに先行する命令型言語に対する優れた抽象化であったため、プログラマを誤った参照から保護するという考えが思い浮かばなかったと思います。

一部のコンパイラまたは追加のツールは、無効なポインタアクセスに対するチェック手段を提供できると考えています。そのため、他の人々はこの潜在的な問題を指摘し、それに対する保護策を講じています。

許可するかどうかは、言語で何を達成したいのか、言語のユーザーにどの程度の責任を負わせたいのかによって異なります。また、コンパイラーを作成してそのタイプの動作を制限する能力にも依存します。

質問に答えるには:

  1. 「どのような議論…」-まあ、それはあなたが言語に何をして欲しいかによって異なります。ベアメタルアクセスをシミュレートする場合は、許可することをお勧めします。

  2. 「それは単なる歴史的な荷物ですか?」おそらく、そうではないでしょう。 null確かに多くの言語に意味があり、それらの言語の表現を促進するのに役立ちます。歴史的な先例は、より最近の言語とその許可に影響を与えたかもしれませんがnull、手を振って、概念が役に立たない歴史的な手荷物であると宣言するのは少し大変です。


1 このウィキペディアの記事を参照してください。ただし、Hoareにはnull値とオブジェクト指向言語の功績が認められています。命令型言語は、アルゴルとは異なる家系図に沿って進歩したと思います。


ポイントは、たとえばC#やJavaのほとんどの変数にnull参照を割り当てることができるということです。参照がないことを「たぶん」明示的に示すオブジェクトにのみヌル参照を割り当てる方がはるかに良いようです。したがって、私の質問は「概念」ヌルについてであり、言葉ではありません。
アオガガビア

2
「ヌルポインタ参照はコンパイル中にエラーとして表示される可能性があります」どのコンパイラがそれを実行できますか?
svick

完全に公平にするために、オブジェクトにnull参照を割り当てることは決してありません...オブジェクトへの参照は存在しません(参照は定義なしで(0x000000000)を指しますnull)。
mgw854

C99仕様の引用では、nullポインターではなくnull文字について説明していますが、これらは2つの非常に異なる概念です。
svick

2
@ GlenH7それは私のためにそれをしません。コードobject o = null; o.ToString();はVS2012でエラーや警告なしで問題なくコンパイルされます。ReSharperはそれについて文句を言いますが、それはコンパイラではありません。
svick

7

引用した記事の例を見ると、ほとんどの場合Maybe、コードを短縮することはできません。確認する必要はありませんNothing。唯一の違いは、型システムを介してそうすることを思い出させることです。

注、私は「リマインド」と言いますが、強制ではありません。プログラマーは怠け者です。プログラマーは、値はにならない可能性があると確信している場合、チェックせずNothingに参照Maybeを解除します。これは、現在NULLポインターを逆参照しているのと同じです。最終結果は、nullポインター例外を「参照解除された空の多分」例外に変換することです。

プログラミング言語がプログラマーに何かを強制しようとする他の分野でも、人間の性質の同じ原則が適用されます。たとえば、Javaデザイナーはほとんどの例外を処理するように人々に強制しようとしました。その結果、例外を静かに無視するか盲目的に伝播する多くの定型文が作成されました。

何がMaybe意思決定の多くは、パターンマッチングや多型の代わりに、明示的なチェックを経て行われたときに素敵なことはあります。たとえば、別の関数processData(Some<T>)とを作成できますがprocessData(Nothing<T>)、これはで実行できませんnull。エラー処理を別の関数に自動的に移動します。これは、関数が常にトップダウン方式で呼び出されるのではなく、関数が渡されて遅延評価される関数型プログラミングでは非常に望ましいことです。OOPでは、エラー処理コードを分離するための好ましい方法は例外を使用することです。


新しいオブジェクト指向言語で望ましい機能になると思いますか?
アオガガビア

ポリモーフィズムの利点を得るには、自分で実装できます。言語サポートが必要なのは、Null不可です。それを自分で指定する方法を見つけたいのですconstが、これはオプションですが、たとえば、リンクリストのような一部の低レベルコードは、null不可オブジェクトを実装するのは非常に面倒です。
カールビーレフェルト

2
ただし、Maybeタイプを確認する必要はありません。それが機能していなければならないので、多分タイプがモナドであるmap :: Maybe a -> (a -> b)bind :: Maybe a -> (a -> Maybe b)あなたが継続している場合else文を使用して作り直すとの大部分は、さらに計算を通すことができるように、その上に定義されています。またgetValueOrDefault :: Maybe a -> (() -> a) -> a、null許容のケースを処理できます。Maybe a明示的にパターンマッチングを行うよりもはるかにエレガントです。
DetriusXii 14

1

Maybe問題を考える非常に機能的な方法です。あるものがあり、定義された値を持っている場合と持っていない場合があります。ただし、オブジェクト指向の意味では、物の概念を(値があるかどうかに関係なく)オブジェクトに置き換えます。明らかに、オブジェクトには値があります。そうでない場合、オブジェクトはnullであると言いますが、本当に意味するのは、オブジェクトがまったくないということです。オブジェクトへの参照は何も指していません。Maybeオブジェクト指向の概念に変換しても、目新しいことはありません。実際、コードが非常に乱雑になります。あなたはまだの値のnull参照を持っている必要がありますMaybe<T>。nullチェックを実行する必要があります(実際、コードを乱雑にするため、さらに多くのnullチェックを実行する必要があります)。確かに、著者が主張するように、より堅牢なコードを記述しますが、それは、ほとんどの場合不要なレベルの作業を必要とする、はるかに抽象的で鈍い言語にしたためだと主張します。新しい変数にアクセスするたびにMaybeチェックを行うスパゲッティコードを扱うよりも、たまにNullReferenceExceptionを喜んで受け取ります。


2
Maybe <T>が表示されるかどうかを確認するだけで、残りの型について心配する必要がないため、nullチェックが少なくなると思います。
アオガガビア

1
値が存在しない可能性があるため、@ svick Maybe<T>null値として許可する必要があります。私が持っている場合はMaybe<MyClass>、それが値を持っていない、値フィールドはNULL参照が含まれている必要があります。検証可能なほど安全なものは他にありません。
mgw854

1
@ mgw854確かにあります。OO言語でMaybeは、2つのクラスを継承する抽象クラスである可能性があります:(Some値のフィールドがある)とNone(そのフィールドがない)。そうすれば、値はneverになりnullます。
svick

6
デフォルトでは「非ヌル可能性」であり、Maybe <T>を使用すると、一部の変数に常にオブジェクトが含まれることを確認できます。<T>たぶんありませんつまり、すべての変数
aochagavia

3
@ mgw854この変更のポイントは、開発者の表現力を高めることです。現在、開発者は常に、参照またはポインターがnullである可能性があり、使用可能な値があることを確認するためにチェックを行う必要があると想定する必要があります。この変更を実装することにより、開発者は有効な値が本当に必要であると発言し、有効な値が渡されたことを確認するためのコンパイラチェックを開発者に与えることができます。しかし、オプトインし、値ではない値を渡すための、開発者向けオプションを引き続き提供しています。
陶酔

1

の概念はnullCに簡単にさかのぼることができますが、それは問題のある場所ではありません。

私が日常的に選択している言語はC#でありnull、1つ違いがあります。C#には2種類のタイプ、値があります参照というあります。値は決してあり得ませんがnull、値がまったく問題ないことを表現したい場合があります。これを行うには、C#はNullable型を使用intするため、値とint?NULL入力可能値になります。これが、参照型も同様に機能するはずだと思う方法です。

参照: NULL参照は間違いではない可能性があります

NULL参照は有用であり、時には不可欠です(C ++で文字列を返す場合と返さない場合の問題を考慮してください)。間違いは、nullポインターの存在ではなく、型システムがそれらを処理する方法にあります。残念ながら、ほとんどの言語(C ++、Java、C#)はそれらを正しく処理しません。


0

関数型プログラミングは、 、特にオブジェクト指向プログラミングよりも他の型(タプル、ファーストクラス型としての関数、モナドなど)を組み合わせた型(または少なくとも最初はそうだった型)に。

あなたが話していると思うプログラミング言語の最新バージョン(C ++、C#、Java)はすべて、汎用プログラミング(C、C#1.0、Java 1)の形式を持たない言語に基づいています。それなしでも、null可能オブジェクトと非null可能オブジェクトの何らかの違いを言語に焼き付けることができます(C ++参照など、できませんがnull、制限されています)が、それははるかに自然ではありません。


関数型プログラミングの場合は、FPが参照またはポインター型を持たない場合だと思います。FPでは、すべてが価値です。そして、ポインタ型がある場合、「ポインタを何もない」と言うのは簡単になります。
陶酔

0

基本的な理由は、プログラムをデータ破損から「安全」にするために必要なnullチェックが比較的少ないからだと思います。プログラムが有効な参照を使用して書き込まれたはずの配列要素またはその他の格納場所のコンテンツを使用しようとした場合、最良の結果は例外がスローされることです。理想的には、例外は問題が発生した場所を正確に示しますが、重要なことは、データ参照を引き起こす可能性のある場所にnull参照が格納される前に何らかの例外がスローされることです。メソッドが最初に何らかの方法でオブジェクトを使用しようとせずにオブジェクトを保存しない限り、オブジェクトを使用しようとすると、それ自体で、「nullチェック」が行われます。

null参照が必要な場所に表示されるnull参照が以外の特定の例外を発生させないようにしたい場合NullReferenceException、多くの場合、場所全体にnullチェックを含める必要があります。一方、null参照が既に行われたものを超える「損傷」を引き起こす前に何らかの例外が発生することを確認するだけで、多くの場合、比較的少ないテストが必要になります。テストは一般に、オブジェクトがそれを使用しようとせずに、参照、およびいずれかのヌル参照は有効なものを上書きする、でしょうそれがプログラム状態の他の側面を誤って解釈するために、他のコードを引き起こします。そのような状況は存在しますが、それほど一般的ではありません。ほとんどの偶発的なヌル参照は非常に迅速にキャッチされますそれらをチェックするかどうか


この投稿は読みにくい(テキストの壁)。それをより良い形に編集してもいいですか?
ブヨ

1
@gnat:それは良いですか?
supercat

0

Maybe」と書かれているように、nullよりも高いレベルの構成体です。それを定義するために、より多くの単語を使用して、「何かへのポインター、または何へのポインターでもないが、コンパイラーは、どれを決定するのに十分な情報をまだ与えられていない」。これにより、記述したコードに追いつくのに十分スマートなコンパイラ仕様を構築しない限り、各値を常に明示的にチェックする必要があります。

nullのある言語でMaybeの実装を簡単に作成できます。C ++には、という形式がありboost::optional<T>ます。多分、nullに相当するものを作るのは非常に難しいです。特に、を持っている場合Maybe<Just<T>>、nullに割り当てることはできません(そのような概念が存在しないため)。一方、T**nullを持つ言語では、nullに割り当てるのは非常に簡単です。これによりMaybe<Maybe<T>>、完全に有効なが使用されますが、そのオブジェクトを使用するためにさらに多くのチェックを行う必要があります。

nullは未定義の動作または例外処理のいずれかを必要とするため、機能言語の一部はMaybeを使用します。どちらも機能言語構文にマッピングするのは簡単な概念ではありません。そのような機能的な状況では、おそらくその役割をはるかに満たすかもしれませんが、手続き型言語では、nullが重要です。それは善悪の問題ではなく、単にあなたがやりたいことをコンピュータに伝えるのが簡単になる理由だけです。

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