nullのない言語の最良の説明


225

プログラマーがnullエラー/例外について不平を言っているとき、誰かがnullなしで何をするかを尋ねられます。

私はオプションタイプのクールさについていくつかの基本的な考えを持っていますが、それを最もよく表現する知識や言語スキルがありません。何が偉大な私たちはその人の方を指すことができると平均プログラマに親しみの方法で書かれた以下の説明は?

  • 参照/ポインタがデフォルトでnull可能になるのは望ましくない
  • 次のようなnullのケースを簡単にチェックするための戦略を含む、オプションタイプの仕組み
    • パターンマッチングと
    • モナディック内包表記
  • メッセージを食べないなどの代替ソリューション
  • (私が逃した他の側面)

11
関数型プログラミングまたはF#のためにこの質問にタグを追加すると、素晴らしい答えが得られます。
スティーブンスウェンセン(

オプションタイプはmlの世界から来たので、関数型プログラミングタグを追加しました。F#にマークを付けたくない(具体的すぎる)。ところで、分類力を持つ誰かは、多分タイプまたはオプションタイプのタグを追加する必要があります。
Roman A. Taycher

4
そのような特定のタグはほとんど必要ないと思います。タグは、主に人々が関連する質問を見つけることを可能にするためのものです(たとえば、「私はよく知っている質問に答えることができます」、「関数型プログラミング」はそこで非常に役立ちます。しかし、「null」や「 「option-type」はそれほど有用ではありません。「option-type」タグを監視して、回答できる質問を探す人はほとんどいないでしょう。;)
jalf

nullの主な理由の1つは、コンピューターが進化して集合論に強く結びついていることであることを忘れないでください。ヌルは、すべての集合論において最も重要な集合の1つです。これがないと、アルゴリズム全体が壊れてしまいます。たとえば、マージソートを実行します。これには、リストを数回に半分に分割することが含まれます。リストの長さが7アイテムの場合はどうなりますか?まず、それを4と3に分割します。次に、2、2、2、1を選択します。次に、1、1、1、1、1、1、1、...とnullです。Nullには目的がありますが、実際には見られない目的があります。それは理論的な領域のためにより多く存在します。
stevendesu

6
@steven_desu-同意しません。「null許容」言語では、空のリスト[]への参照とnullリスト参照を含めることができます。この質問は、2つの間の混乱に関連しています。
stusmith

回答:


433

nullが望ましくない理由を簡潔にまとめると、無意味な状態は表現できないはずです。

ドアをモデリングしているとしましょう。3つの状態のいずれかになります。開いている、閉じているがロックされていない、閉じられてロックされている。これで、次のようにモデル化できます

class Door
    private bool isShut
    private bool isLocked

そして、私の3つの状態をこれら2つのブール変数にマッピングする方法は明らかです。しかし、これにより、4番目の望ましくない状態が利用可能になりますisShut==false && isLocked==true。私の表現として選択した型はこの状態を許可するため、クラスがこの状態にならないようにするために(おそらく、不変条件を明示的にコーディングすることによって)精神的な労力を費やす必要があります。対照的に、代数的データ型またはチェックされた列挙型を使用して言語を定義していた場合、

type DoorState =
    | Open | ShutAndUnlocked | ShutAndLocked

それから私は定義することができます

class Door
    private DoorState state

心配はもうありません。型システムは、のインスタンスが存在する可能性のある状態が3つしかないことを保証しますclass Door。これが、型システムが得意とするところであり、コンパイル時にエラーのクラス全体を明示的に除外します。

の問題nullは、すべての参照タイプが、通常は望ましくないスペースでこの余分な状態を取得することです。string変数は任意の文字列可能性があり、またはそれは、このクレイジーな余分な可能性がありnull、私の問題領域にマッピングされない値。Triangle対象は3持っているPoint自身が持っているの、XYの値を、残念ながらPointSまたはTriangle自体は私が働いているグラフのドメインに意味があり、このクレイジーなNULL値であるかもしれない。等

存在しない可能性のある値をモデル化する場合は、明示的に選択する必要があります。私が人々をモデル化しようとしている方法が、すべてPersonFirstNameとを持っているというものであるがLastName、一部の人々だけがを持っているMiddleNameという場合、

class Person
    private string FirstName
    private Option<string> MiddleName
    private string LastName

どこstringここでは、非NULL可能タイプであると想定されます。そうNullReferenceExceptionすれば、誰かの名前の長さを計算しようとするときに確立するトリッキーな不変条件や予期しないsはありません。任意のコードを扱うこと型システム性を保証MiddleNameがあるという可能性のためにアカウントNoneを扱う任意のコードに対し、FirstName安全に存在価値があると仮定することができます。

したがって、たとえば、上記のタイプを使用して、この愚かな関数を作成できます。

let TotalNumCharsInPersonsName(p:Person) =
    let middleLen = match p.MiddleName with
                    | None -> 0
                    | Some(s) -> s.Length
    p.FirstName.Length + middleLen + p.LastName.Length

心配ありません。対照的に、文字列などの型のnull可能な参照を持つ言語では、

class Person
    private string FirstName
    private string MiddleName
    private string LastName

あなたは次のものをオーサリングします

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length + p.MiddleName.Length + p.LastName.Length

着信するPersonオブジェクトにすべてがnullではないという不変式がない場合に爆発します。

let TotalNumCharsInPersonsName(p:Person) =
    (if p.FirstName=null then 0 else p.FirstName.Length)
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + (if p.LastName=null then 0 else p.LastName.Length)

または多分

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + p.LastName.Length

pfirst / lastが確実に存在するが、middleがnullである可能性があると仮定したり、さまざまなタイプの例外をスローするチェックを実行したり、誰が何を知っているかを想定したりします。これらのクレイジーな実装の選択と考えるべきことはすべて、あなたが望まない、または必要としないこの愚かな表現可能な値があるためです。

Nullは通常、不必要な複雑さを追加します。 複雑さはすべてのソフトウェアの敵であり、合理的である場合はいつでも複雑さを軽減するよう努めるべきです。

(これらの単純な例でさえ、より複雑であることに注意してください。a FirstNameがでなくてもnull、(空の文字列)をstring表すことができ""ます。これは、おそらく私たちがモデル化しようとしている人物の名前ではありません。 nullable文字列、それでも「意味のない値を表す」場合があるかもしれません。繰り返しますが、実行時に不変条件と条件付きコードを介して、または型システムを使用して(たとえばNonEmptyString型を持つために)これと戦うことを選択できます。後者はおそらく不適切なアドバイスです(「良い」タイプは多くの場合、一連の一般的な操作で「クローズ」されます。たとえばNonEmptyString.SubString(0,0))、しかし、それはデザインスペースのより多くのポイントを示しています。結局のところ、特定の型システムでは、それを取り除くのに非常に優れた複雑性と、本質的に取り除くのが難しい他の複雑さがあります。このトピックの要点は、ほぼすべての型システムで、「デフォルトでnull可能参照」から「デフォルトでnull不可参照」への変更が、ほとんど常に単純な変更であり、タイプシステムが複雑さとの戦いにおいて非常に優れていることです。特定のタイプのエラーと無意味な状態を除外する。したがって、非常に多くの言語がこのエラーを何度も繰り返し続けるのはかなりおかしいです。)


31
Re:名前-確かに。また、ドアが開いたままで、ロックデッドボルトが突き出してドアが閉まらないようにしたドアをモデル化することに関心があるかもしれません。世界には多くの複雑さがあります。キーが追加することではない、よりお使いのソフトウェアで、「世界の状態」と「プログラム状態」との間のマッピングを実装する際の複雑さを。
ブライアン

59
何、ドアを開けるのをロックしたことがないの?
ジョシュア

58
なぜ人々が特定のドメインのセマンティクスを検討するのか理解できません。ブライアンは欠陥を簡潔で簡単な方法でnullで表現しました。そうです、彼は彼の例で問題のドメインを単純化しました。質問は「T」、ブライアンに答えられました-あなたがボストンにいるなら、私はあなたがここであなたがするすべての投稿のためにビールを借りています!
akaphenom

67
@akaphenom:ありがとう。ただし、すべての人がビールを飲むわけではないことに注意してください(私は非飲酒者です)。しかし、感謝の気持ちを伝えるために世界の単純化されたモデルを使用していることを感謝します。そのため、私はあなたの世界モデルの欠陥のある仮定についてこれ以上説明しません。:P(実世界では非常に複雑です!:))
Brian

4
不思議なことに、この世界には3ステートドアがあります。一部のホテルではトイレのドアとして使用されています。押しボタンは内側からのキーとして機能し、ドアを外側からロックします。ラッチボルトが移動するとすぐに、自動的にロックが解除されます。
comonad

65

オプションタイプの良い点は、オプションであることではありません。これは、ということである他のすべてのタイプではありません

時には、ある種の「ヌル」状態を表現できる必要があります。場合によっては、「値なし」オプションと、変数が取る可能性のある他の可能な値を表す必要があります。したがって、これを完全に拒否する言語は、少し不自由になるでしょう。

しかし、多くの場合、私たちはそれを必要とせず、そのような「null」状態を許可することは曖昧さと混乱につながるだけです。.NETで参照型変数にアクセスするたびに、それがnullである可能性があることを考慮する必要があります

多くの場合、プログラマーがコードを構造化してそれが決して起こらないようにするため、実際にヌルなることはありません。しかし、コンパイラーはそれを検証できません。また、それを見るたびに、「これはnullである可能性がありますか?ここでnullをチェックする必要がありますか?」と自問する必要があります。

理想的には、nullが意味を成さない多くの場合、許可されるべきではありません

これは、ほとんどすべてがnullになる可能性がある.NETで実現するのが難しいです。呼び出しているコードの作成者が100%統制され、一貫性があり、nullにできるものとnullにできないものを明確に文書化する必要があります。または、偏執的ですべてをチェックする必要があります

ただし、型がデフォルトでnullにできない場合は、nullかどうかを確認する必要はありません。コンパイラー/タイプチェッカーが強制的にnullを設定するため、nullになることはありません。

そして、ヌル状態を処理する必要があるまれなケースのためのバックドア必要です。次に、「オプション」タイプを使用できます。次に、「値なし」のケースを表すことができるようにする必要があるという意識的な決定を行った場合はnullを許可し、それ以外の場合はすべて、値がnullになることはないことを知っています。

他の人が述べたように、たとえばC#またはJavaでは、nullは次の2つのいずれかを意味します。

  1. 変数は初期化されていません。これは理想的に起こらないはずです。変数はは、初期化されない限り存在しては。
  2. 変数には「オプションの」データが含まれていますデータがない場合を表すことができる必要があります。これは時々必要です。おそらく、リスト内のオブジェクトを見つけようとしていて、オブジェクトがそこにあるかどうかを事前に知ることができません。次に、「オブジェクトが見つかりませんでした」と表現できる必要があります。

2番目の意味は保持する必要がありますが、1番目の意味は完全に削除する必要があります。そして、2番目の意味でさえ、デフォルトであってはなりません。これは、必要なときに必要なときにオプトインできるものです。ただし、何かをオプションにする必要がない場合は、型チェッカーがnullにならないことを保証する必要があります。


2番目の意味では、最初にnullかどうかをチェックせずにそのような変数にアクセスしようとすると、コンパイラーに警告(停止?)を要求します。ここでは、今後のヌル/ null以外のC#の機能についての素晴らしい記事だ(最終的には!)blogs.msdn.microsoft.com/dotnet/2017/11/15/...は
オハッドシュナイダー

44

これまでのすべての回答nullは、なぜ悪いことであるか、特定の値がnullにならないことを言語が保証できる場合にどれほど便利であるかに焦点を当てています。

次に、すべての値に非null可能性を適用することはかなり良いアイデアであると提案し続けます。これは、Optionまたはのような概念を追加した場合に実行できますMaybe、常に値が定義されているとは限らないタイプを表すます。これはHaskellが採用したアプローチです。

それはすべて良いものです!ただし、同じ効果を達成するために明示的にnullを使用できる/ null以外の型を使用することを妨げるものではありません。では、なぜOptionはまだ良いのでしょうか?(すべての後に、ScalaはNULL可能値をサポートしていますが、それはサポートのJavaライブラリで作業できるように)Optionsなども。

Q.では、言語からnullを完全に削除できること以外にどのような利点があるのでしょうか。

A.構成

null対応のコードから単純な変換を行う場合

def fullNameLength(p:Person) = {
  val middleLen =
    if (null == p.middleName)
      p.middleName.length
    else
      0
  p.firstName.length + middleLen + p.lastName.length
}

オプション対応コードへ

def fullNameLength(p:Person) = {
  val middleLen = p.middleName match {
    case Some(x) => x.length
    case _ => 0
  }
  p.firstName.length + middleLen + p.lastName.length
}

大した違いはありません!しかし、それはオプションを使用するためのひどい方法でもあります...このアプローチははるかにきれいです:

def fullNameLength(p:Person) = {
  val middleLen = p.middleName map {_.length} getOrElse 0
  p.firstName.length + middleLen + p.lastName.length
}

あるいは:

def fullNameLength(p:Person) =       
  p.firstName.length +
  p.middleName.map{length}.getOrElse(0) +
  p.lastName.length

オプションのリストを扱い始めると、さらに良くなります。リストpeople自体はオプションであると想像してください:

people flatMap(_ find (_.firstName == "joe")) map (fullNameLength)

これはどのように作動しますか?

//convert an Option[List[Person]] to an Option[S]
//where the function f takes a List[Person] and returns an S
people map f

//find a person named "Joe" in a List[Person].
//returns Some[Person], or None if "Joe" isn't in the list
validPeopleList find (_.firstName == "joe")

//returns None if people is None
//Some(None) if people is valid but doesn't contain Joe
//Some[Some[Person]] if Joe is found
people map (_ find (_.firstName == "joe")) 

//flatten it to return None if people is None or Joe isn't found
//Some[Person] if Joe is found
people flatMap (_ find (_.firstName == "joe")) 

//return Some(length) if the list isn't None and Joe is found
//otherwise return None
people flatMap (_ find (_.firstName == "joe")) map (fullNameLength)

nullチェック(またはelvis?:演算子)を使用した対応するコードは、非常に長くなります。ここでの本当のトリックは、null許容値が決して達成できない方法で、オプションとコレクションのネストされた理解を可能にするflatMap操作です。


8
+1、これは強調すべき良い点です。補遺の1つ:HaskellランドでflatMapは、、(>>=)つまりモナドの「バインド」演算子が呼び出されます。そうです、HaskellerはflatMappingを非常に気に入っているので、言語のロゴに入れています。
CAマッキャン

1
+1うまくいけば、の表現Option<T>は決して、決してヌルになることはありません。悲しいことに、Scalaはうーん、まだJavaにリンクされています:-)(一方、ScalaがJavaでうまく機能しない場合、誰がそれを使用しますか?Oo)

実行するのに十分簡単: 'List(null).headOption'。これは 'None'の戻り値とは非常に異なることを意味することに注意してください
Kevin Wright

4
私はあなたが作曲についてあなたが言ったことを本当に気に入っているので、他の人々が言及していないようにあなたに賞金を与えました。
ローマンA.テイチャー

素晴らしい例で素晴らしい答え!
thSoft 2011

38

人々はそれを見逃しているように見えるので、nullあいまいです。

アリスの生年月日はnullです。どういう意味ですか?

ボブの死亡日は nullです。どういう意味ですか?

「合理的な」解釈としては、アリスの生年月日は存在するが不明であるのに対し、ボブの生年月日は存在しない(ボブはまだ生きている)場合があります。しかし、なぜ私たちは別の答えを得たのですか?


別の問題:nullエッジケースです。

  • ですかnull = null
  • ですかnan = nan
  • ですかinf = inf
  • ですか+0 = -0
  • です +0/0 = -0/0

答えは通常、それぞれ「はい」、「いいえ」、「はい」、「はい」、「いいえ」、「はい」です。クレイジーな「数学者」はNaNを「無効」と呼び、それは自分自身に等しいと言います。SQLはnullを何にも等しくないものとして扱います(そのため、nullはNaNのように動作します)。±∞、±0、およびNaNを同じデータベース列に格納しようとするとどうなるのか不思議に思います(2 53 NaNがあり、その半分は「負」です)。

さらに悪いことに、データベースはNULLの扱い方が異なり、それらのほとんどは一貫性がありません(SQLiteでのNULL処理を参照)。概要についてでの)。それはかなり恐ろしいです。


そして今、義務的な話のために:

私は最近、5列の(sqlite3)データベーステーブルを設計しましたa NOT NULL, b, id_a, id_b NOT NULL, timestamp。これはかなり任意のアプリの一般的な問題を解決するために設計された一般的なスキーマであるため、2つの一意性の制約があります。

UNIQUE(a, b, id_a)
UNIQUE(a, b, id_b)

id_a既存のアプリデザインとの互換性のためにのみ存在し(一部の優れたソリューションを思い付かなかったため)、新しいアプリでは使用されません。SQLでのNULLの動作方法のため、最初の一意性制約を挿入(1, 2, NULL, 3, t)(1, 2, NULL, 4, t)ても違反しません(のため(1, 2, NULL) != (1, 2, NULL))。

これは、ほとんどのデータベースの一意性制約でNULLがどのように機能するかによって特に機能します(おそらく、「実際の」状況をモデル化するのが簡単になるでしょう。たとえば、2人が同じ社会保障番号を持つことはできませんが、すべての人が1つを持っているわけではありません)。


FWIW、最初に未定義の動作を呼び出さない限り、C ++参照はnullを「ポイント」できず、初期化されていない参照メンバー変数でクラスを構築することはできません(例外がスローされた場合、構築は失敗します)。

補足:架空のiOSなど、相互に排他的なポインターが必要になる場合があります(つまり、そのうちの1つだけが非NULLになる可能性があります)type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed。代わりに、私はのようなことを強いられますassert((bool)actionSheet + (bool)alertView == 1)


実際の数学者は「NaN」の概念を使用していませんが、安心してください。
Noldorin 2012

@Noldorin:彼らはそうしますが、彼らは「不定形」という用語を使用します。
IJケネディ

@IJKennedy:それは別の大学です。一部の 'NaN'は不確定な形式を表す場合がありますが、FPAはシンボリックな推論を行わないため、それを不確定な形式と同じにするのはかなり誤解を招きます。
Noldorin 2012

何が問題になっていassert(actionSheet ^ alertView)ますか?それとも、あなたの言語のXORブール値は使えませんか?

16

参照/ポインタを持つことの望ましくないことは、デフォルトでnull可能です。

私はこれがヌルの主な問題だとは思わない、ヌルの主な問題はそれらが2つのことを意味する可能性があるということです:

  1. 参照/ポインターは初期化されていません。ここでの問題は、一般に可変性と同じです。まず、コードの分析が難しくなります。
  2. 変数がnullであることは、実際には何かを意味します。これは、Option型が実際に形式化する場合です。

通常、オプションタイプをサポートする言語は、初期化されていない変数の使用も禁止または推奨します。

オプションタイプがどのように機能するか(パターンマッチングなどのnullケースのチェックを容易にする方法を含む)。

効果的にするために、オプションタイプは言語で直接サポートされる必要があります。それ以外の場合、それらをシミュレートするには多くのボイラープレートコードが必要です。パターンマッチングとタイプ推論は、オプションタイプを扱いやすくする2つの主要な言語機能です。例えば:

F#の場合:

//first we create the option list, and then filter out all None Option types and 
//map all Some Option types to their values.  See how type-inference shines.
let optionList = [Some(1); Some(2); None; Some(3); None]
optionList |> List.choose id //evaluates to [1;2;3]

//here is a simple pattern-matching example
//which prints "1;2;None;3;None;".
//notice how value is extracted from op during the match
optionList 
|> List.iter (function Some(value) -> printf "%i;" value | None -> printf "None;")

ただし、Option型を直接サポートしていないJavaのような言語では、次のようになります。

//here we perform the same filter/map operation as in the F# example.
List<Option<Integer>> optionList = Arrays.asList(new Some<Integer>(1),new Some<Integer>(2),new None<Integer>(),new Some<Integer>(3),new None<Integer>());
List<Integer> filteredList = new ArrayList<Integer>();
for(Option<Integer> op : list)
    if(op instanceof Some)
        filteredList.add(((Some<Integer>)op).getValue());

メッセージを食べないなどの代替ソリューション

Objective-Cの「nilを食べるメッセージ」は、nullチェックの頭痛を軽減する試みとしてはそれほど解決策ではありません。基本的に、nullオブジェクトでメソッドを呼び出そうとしたときにランタイム例外をスローする代わりに、式はnullと評価されます。信じられない思いを止めて、まるで各インスタンスメソッドがで始まるかのようですif (this == null) return null;。ただし、情報が失われます。メソッドがnullを返したかどうかは、メソッドが有効な戻り値であるか、オブジェクトが実際にnullであるかがわかりません。これは例外の飲み込みによく似ており、前に概説したnullの問題への対処は進んでいません。


これは大したことではありませんが、c#はcのような言語ではありません。
Roman A. Taycher

4
ここではJavaを使用していました。おそらくC#の方が優れた解決策になるからです...でも、皆さんが本当に喜んでいることに感謝します。私は先に進み、「cのような」ステートメントを置き換えました。
Stephen Swensen、

linqで、そうです。私はc#について考えていましたが、それに気付きませんでした。
Roman A. Taycher

1
はい、ほとんどcにインスパイアされた構文ですが、関数型プログラマーがc-likeと呼ぶc-like構文がほとんどないpython / ruby​​などの命令型プログラミング言語についても聞いたことがあります。
ローマンA.テイチャー

11

アセンブリは、型なしポインタとも呼ばれるアドレスをもたらしました。Cはそれらを型付きポインターとして直接マッピングしましたが、すべての型付きポインターと互換性のある一意のポインター値としてAlgolのnullを導入しました。Cのnullの大きな問題は、すべてのポインターがnullになる可能性があるため、手動でチェックしないとポインターを安全に使用できないことです。

高水準言語では、nullは2つの異なる概念を実際に伝えるため、nullは扱いにくくなります。

  • 何かが定義されていないことを伝える。
  • 何かがオプションであることを伝える。

未定義の変数があると、ほとんど役に立たず、それらが発生するたびに未定義の動作が発生します。私は誰もが定義されていないものを持つことは絶対に避けられるべきであることに同意するでしょう。

2番目のケースはオプションであり、オプションタイプなどで明示的に提供するのが最適です。


私たちが運輸会社にいて、ドライバーのスケジュールを作成するためのアプリケーションを作成する必要があるとします。ドライバーごとに、運転免許証や緊急時に呼び出す電話番号など、いくつかの情報を保存しています。

Cでは、次のようになります。

struct PhoneNumber { ... };
struct MotorbikeLicence { ... };
struct CarLicence { ... };
struct TruckLicence { ... };

struct Driver {
  char name[32]; /* Null terminated */
  struct PhoneNumber * emergency_phone_number;
  struct MotorbikeLicence * motorbike_licence;
  struct CarLicence * car_licence;
  struct TruckLicence * truck_licence;
};

ご覧のように、ドライバーのリストに対する処理では、nullポインターをチェックする必要があります。コンパイラはあなたを助けません、プログラムの安全性はあなたの肩にかかっています。

OCamlでは、同じコードは次のようになります。

type phone_number = { ... }
type motorbike_licence = { ... }
type car_licence = { ... }
type truck_licence = { ... }

type driver = {
  name: string;
  emergency_phone_number: phone_number option;
  motorbike_licence: motorbike_licence option;
  car_licence: car_licence option;
  truck_licence: truck_licence option;
}

ここで、すべての運転手の名前とトラックの免許番号を印刷するとします。

C:

#include <stdio.h>

void print_driver_with_truck_licence_number(struct Driver * driver) {
  /* Check may be redundant but better be safe than sorry */
  if (driver != NULL) {
    printf("driver %s has ", driver->name);
    if (driver->truck_licence != NULL) {
      printf("truck licence %04d-%04d-%08d\n",
        driver->truck_licence->area_code
        driver->truck_licence->year
        driver->truck_licence->num_in_year);
    } else {
      printf("no truck licence\n");
    }
  }
}

void print_drivers_with_truck_licence_numbers(struct Driver ** drivers, int nb) {
  if (drivers != NULL && nb >= 0) {
    int i;
    for (i = 0; i < nb; ++i) {
      struct Driver * driver = drivers[i];
      if (driver) {
        print_driver_with_truck_licence_number(driver);
      } else {
        /* Huh ? We got a null inside the array, meaning it probably got
           corrupt somehow, what do we do ? Ignore ? Assert ? */
      }
    }
  } else {
    /* Caller provided us with erroneous input, what do we do ?
       Ignore ? Assert ? */
  }
}

OCamlでは次のようになります。

open Printf

(* Here we are guaranteed to have a driver instance *)
let print_driver_with_truck_licence_number driver =
  printf "driver %s has " driver.name;
  match driver.truck_licence with
    | None ->
        printf "no truck licence\n"
    | Some licence ->
        (* Here we are guaranteed to have a licence *)
        printf "truck licence %04d-%04d-%08d\n"
          licence.area_code
          licence.year
          licence.num_in_year

(* Here we are guaranteed to have a valid list of drivers *)
let print_drivers_with_truck_licence_numbers drivers =
  List.iter print_driver_with_truck_licence_number drivers

この簡単な例でわかるように、安全なバージョンでは複雑なことは何もありません。

  • より簡潔です。
  • はるかに優れた保証が得られ、nullチェックはまったく必要ありません。
  • コンパイラは、オプションを正しく処理することを保証しました

Cでは、nullチェックとブームを忘れただけかもしれません...

注:これらのコードサンプルはコンパイルされていませんが、アイデアが得られれば幸いです。


私は試したことはありませんが、en.wikipedia.org / wiki / Cyclone_%28programming_language%29はcにnull以外のポインタを許可すると主張しています。
ローマンA.テイチャー、

1
最初のケースに誰も興味がないというあなたの発言には同意しません。多くの人々、特に関数型言語コミュニティの人々はこれに非常に興味があり、初期化されていない変数の使用を阻止するか完全に禁止します。
Stephen Swensen、

私はNULL、「何も指さない可能性のある参照」がいくつかのアルゴル語のために発明されたと信じています(Wikipediaも同意します。en.wikipedia.org/ wiki / Null_pointerNull_pointerを参照してください)。しかし、もちろん、アセンブリプログラマーがポインターを無効なアドレスに初期化した可能性があります(読み取り:Null = 0)。

1
@Stephen:おそらく同じことを意味していました。私には、未定義のものを使用することを妨げたり、禁止したりしています。なぜなら、未定義のものについては、正気で役に立たないため、議論する意味がないからです。それはまったく興味がありません。
bltxd 2010年

2
@tcとして。nullはアセンブリとは関係ありません。アセンブリでは、型は通常null可能ではありません。汎用レジスターにロードされた値がゼロの場合や、ゼロ以外の整数の場合があります。ただし、nullになることはありません。メモリアドレスをレジスタにロードしても、ほとんどの一般的なアーキテクチャでは、「ヌルポインタ」を個別に表現することはできません。それはC.のような、より高いレベルの言語で導入された概念だ
jalf

5

Microsoft Researchには、

スペック番号

これは、nullはないタイプオブジェクトがnullでないこと確認するメカニズムを備えたC#拡張機能ですが、私見では、コントラクトの原則による設計を適用する方が、null参照によって引き起こされる多くの厄介な状況に適している場合があります。


4

.NETのバックグラウンドから来た私は、nullには意味があるといつも思っていました。私が構造体を知り、構造体を使ってボイラープレートコードを回避するのがいかに簡単かを知るまでは。2009年にQCon Londonで講演したTony Hoare、null参照の発明について謝罪しました。彼を引用するには:

私はそれを10億ドルの間違いと呼んでいます。それは1965年にnull参照の発明でした。そのとき、私はオブジェクト指向言語(ALGOL W)での参照用の最初の包括的な型システムを設計していました。私の目標は、コンパイラーによって自動的に実行されるチェックを使用して、参照のすべての使用が完全に安全であることを確認することでした。しかし、実装が非常に簡単だったという理由だけで、null参照を挿入するという誘惑に抵抗できませんでした。これにより、無数のエラー、脆弱性、システムクラッシュが発生し、おそらく過去40年間で10億ドルの苦痛と損害を引き起こしています。近年、MicrosoftのPREfixやPREfastなどのプログラムアナライザーが参照のチェックに使用され、nullでない可能性がある場合は警告が出されます。Spec#のような最近のプログラミング言語では、null以外の参照の宣言が導入されています。これが私が1965年に却下した解決策です。

プログラマーにもこの質問を見てください



1

私は常にNull(またはnil)を値が存在しないと見なしてきました。

必要な場合とそうでない場合があります。使用しているドメインによって異なります。不在が意味がある場合:ミドルネームがない場合、アプリケーションはそれに応じて動作できます。一方、null値が存在してはならない場合:名がnullの場合、開発者はよく聞かれる午前2時の電話を受けます。

また、コードがオーバーロードされ、nullのチェックが複雑になっていることも確認しました。私にとってこれは2つのことの1つを意味します:
a)アプリケーションツリーの上位にあるバグ
b)不良/不完全な設計

肯定的な側面-Nullはおそらく何かが存在しないかどうかをチェックするためのより有用な概念の1つであり、nullの概念を持たない言語は、データの検証を行うとき、複雑になりすぎます。この場合、新しい変数が初期化されていない場合、通常、上記の言語担当者は変数を空の文字列、0、または空のコレクションに設定します。ただし、空の文字列または0または空のコレクションがアプリケーションにとって有効な値である場合、問題が発生します。

初期化されていない状態を表すためにフィールドの特別な/奇妙な値を発明することでこれが回避されたことがあります。しかし、善意のユーザーが特別な値を入力するとどうなりますか?そして、これがデータ検証ルーチンを混乱させることはありません。言語がnullの概念をサポートしていれば、すべての懸念が消えます。


こんにちは@ジョン、ここであなたをフォローするのは少し難しいです 「特別/奇妙な」値とは、おそらくJavascriptの「未定義」またはIEEEの「NaN」のようなものを意味していることに、ようやく気づきました。しかし、それ以外に、OPが尋ねた質問には実際には対応していません。そして、「Nullはおそらく何かが存在しないかどうかをチェックするための最も有用な概念である」というステートメントはほぼ間違いなく間違っています。オプションタイプは、nullの代わりによく使用され、タイプセーフな方法です。
Stephen Swensen、

@Stephen-実際に私のメッセージを振り返ってみると、後半全体をまだ質問されていない質問に移動する必要があると思います。ただし、nullは、何かが存在しないかどうかを確認するために非常に役立つと言います。
Jon

0

ベクトル言語は、ヌルがないことで回避できる場合があります。

この場合、空のベクトルは型付きnullとして機能します。


私はあなたが話していることを理解していると思いますが、いくつかの例を挙げてもらえますか?特にnullの可能性がある値に複数の関数を適用する場合はどうでしょうか。
Roman A. Taycher

空のベクトルにベクトル変換を適用すると、別の空のベクトルになります。ちなみに、SQLは主にベクトル言語です。
ジョシュア

1
わかりました。SQLは行のベクトル言語であり、列の値言語です。
ジョシュア
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.