Haskellの仕組みは次のとおりです(Haskellはオブジェクト指向言語ではないため、Lippertの発言に対する正確なカウンターではありません)。
警告:深刻なHaskellのファンボーイからの長い回答。
TL; DR
この例は、HaskellとC#の違いを正確に示しています。構造構築のロジスティクスをコンストラクターに委任する代わりに、周囲のコードで処理する必要があります。Nothingnull値はMaybe通常の非non-valueと互換性がない/直接変換できないと呼ばれる特別なラッパータイプ内でのみ発生するため、null(またはHaskellの)値がnull以外の値を予期する場所に切り取られる方法はありません。 nullable型。a Maybeでラップしてnull可能にした値を使用するには、まずパターンマッチングを使用して値を抽出する必要があります。これにより、null以外の値があることが確実にわかっているブランチに制御フローを迂回させることができます。
したがって:
null不可の参照が無効であると判断された状況下では決してないことを常に知ることができますか?
はい。 IntそしてMaybe Int、2つの完全に独立したタイプです。検索Nothing平野では、Int内の文字列「魚」を見つけるに匹敵するだろうInt32。
参照型のnull不可フィールドを持つオブジェクトのコンストラクターではどうでしょうか?
問題ではありません:Haskellの値コンストラクターは、与えられた値を取り、それらをまとめる以外に何もできません。すべての初期化ロジックは、コンストラクターが呼び出される前に実行されます。
そのようなオブジェクトのファイナライザでは、参照を埋めるはずのコードが例外をスローしたためにオブジェクトがファイナライズされますが、どうでしょうか?
Haskellにはファイナライザがないため、これに実際に対処することはできません。ただし、私の最初の応答はまだ有効です。
完全な回答:
Haskellにはnullがなく、Maybeデータ型を使用してnullableを表します。たぶん、このように定義された藻類のデータ型です:
data Maybe a = Just a | Nothing
Haskellに不慣れな方は、これを「A Maybeis a Nothingor a Just a」と読んでください。具体的には:
Maybeあるタイプのコンストラクタ:それは(ジェネリッククラスとして(間違って)考えることができa型変数です)。C#の例えはclass Maybe<a>{}です。
Justは値コンストラクターです。これは、型の引数を1つ受け取り、その値を含むa型の値を返す関数Maybe aです。したがって、コードx = Just 17はに類似していint? x = 17;ます。
Nothingは別の値コンストラクタですが、引数を取らず、Maybe返される値は「Nothing」以外の値を持ちません。x = Nothingは、Haskellのint? x = null;制約をにするaと仮定しますInt。これはを書くことで実行できますx = Nothing :: Maybe Int)。
Maybeタイプの基本が邪魔にならないので、HaskellはOPの質問で議論された問題をどのように回避しますか?
Haskellは、これまでに説明したほとんどの言語とはまったく異なるため、最初にいくつかの基本的な言語の原則を説明します。
まず、Haskellでは、すべてが不変です。すべて。名前は、値を保存できるメモリの場所ではなく、値を参照します(これだけでも、バグを除去するための非常に大きなソースです)。変数宣言と割り当てはHaskellの値に2つの別々の操作は、その値(例えばを定義することによって作成されるC#、とは異なりx = 15、y = "quux"、z = Nothing)、変更しないことができます。したがって、次のようなコード:
ReferenceType x;
Haskellでは不可能です。値を初期化しても問題はありません。nullすべての値が存在するには、値を明示的に初期化する必要があるためです。
第二に、Haskellはオブジェクト指向言語ではない:それは純粋に機能的な言語なので、単語の厳密な意味でないオブジェクトが存在しません。代わりに、引数を取り、統合された構造を返す単純な関数(値コンストラクター)があります。
次に、絶対的なスタイルコードはまったくありません。これにより、ほとんどの言語は次のようなパターンに従います。
do thing 1
add thing 2 to thing 3
do thing 4
if thing 5:
do thing 6
return thing 7
プログラムの動作は一連の指示として表されます。オブジェクト指向言語では、クラスと関数の宣言もプログラムの流れに大きな役割を果たしますが、本質的には、プログラムの実行の「肉」は実行される一連の命令の形を取ります。
Haskellでは、これは不可能です。代わりに、プログラムフローは関数の連鎖によって完全に決定されます。命令型の表記でも、do匿名関数を>>=演算子に渡すための構文上の砂糖です。すべての関数の形式は次のとおりです。
<optional explicit type signature>
functionName arg1 arg2 ... argn = body-expression
body-expression値に評価されるものはどこでもかまいません。明らかにより多くの構文機能が利用可能ですが、主なポイントはステートメントのシーケンスが完全にないことです。
最後に、そしておそらく最も重要なこととして、Haskellの型システムは非常に厳密です。 Haskellの型システムの中心的な設計哲学を要約しなければならないとしたら、「コンパイル時にできる限り多くのことを間違って実行し、実行時にできるだけ問題を起こさない」と言います。一切の暗黙的な変換はありません(促進したいIntのはDouble?使用fromIntegral機能)。実行時に無効な値が発生する可能性があるのは、使用することだけですPrelude.undefined(明らかに存在する必要があり、削除することは不可能です)。
これらすべてを念頭に置いて、amonの「壊れた」例を見て、このコードをHaskellで再表現してみましょう。まず、データ宣言(名前付きフィールドのレコード構文を使用):
data NotSoBroken = NotSoBroken {foo :: Foo, bar :: Bar }
(fooそしてbar実際に機能アクセサ匿名フィールドここではなく、実際のフィールドにしているが、我々は、この詳細を無視することができます)。
NotSoBroken値コンストラクタは、服用以外の任意のアクション取ることができないFooとBar(NULL可能ではありません)となってNotSoBroken、それらのうちに。命令コードを入力したり、フィールドを手動で割り当てたりする場所さえありません。すべての初期化ロジックは、他の場所、ほとんどの場合専用のファクトリー関数で実行する必要があります。
例では、Broken常に構築に失敗します。NotSoBroken同様の方法で値コンストラクターを破壊する方法はありません(コードを記述する場所はありません)が、同様に欠陥のあるファクトリー関数を作成できます。
makeNotSoBroken :: Foo -> Bar -> Maybe NotSoBroken
makeNotSoBroken foo bar = Nothing
(最初の行は型シグネチャ宣言です:引数としてmakeNotSoBrokena Fooとa Barを取り、を生成しますMaybe NotSoBroken)。
戻り値の型があることが必要Maybe NotSoBrokenではなく、単にNotSoBrokenので、我々はに評価するためにそれを告げたNothingため値コンストラクタです、Maybe。別の何かを書いた場合、型は単純に整列しません。
絶対に意味がないことは別として、この関数は実際の目的さえ果たせません。使用しようとするときに見ます。を引数としてuseNotSoBroken期待するという関数を作成しましょうNotSoBroken:
useNotSoBroken :: NotSoBroken -> Whatever
(引数としてuseNotSoBrokena NotSoBrokenを受け入れ、を生成しますWhatever)。
そして次のように使用します:
useNotSoBroken (makeNotSoBroken)
ほとんどの言語では、この種の動作によりNULLポインター例外が発生する場合があります。Haskellでは、型は一致しません:makeNotSoBrokenaを返しますが、a Maybe NotSoBrokenをuseNotSoBroken期待しNotSoBrokenます。これらの型は互換性がなく、コードはコンパイルに失敗します。
これを回避するためcaseに、Maybe値の構造に基づいて分岐するステートメントを使用できます(パターンマッチングと呼ばれる機能を使用)。
case makeNotSoBroken of
Nothing -> --handle situation here
(Just x) -> useNotSoBroken x
明らかにこのスニペットは、実際にコンパイルするために何らかのコンテキスト内に配置する必要がありますが、Haskellがnullableを処理する方法の基本を示しています。上記のコードの段階的な説明は次のとおりです。
- 最初に
makeNotSoBrokenが評価され、typeの値を生成することが保証されMaybe NotSoBrokenます。
- この
caseステートメントは、この値の構造を検査します。
- 値がの
Nothing場合、「ここで状況を処理する」コードが評価されます。
- 値が代わりに値と一致する
Just場合、他のブランチが実行されます。一致する句が、値をJust構成として同時に識別し、その内部NotSoBrokenフィールドを名前(この場合はx)にバインドする方法に注意してください。つまりx、通常のNotSoBroken値のように使用できます。
したがって、パターンマッチングは、オブジェクトの構造が制御の分岐と不可分に結びついているため、型の安全性を強化するための強力な機能を提供します。
これがわかりやすい説明であったことを願っています。意味がわからない場合は、Learn You A Haskell For Great Good!、今まで読んだ中で最高のオンライン言語チュートリアルの1つ。この言語で私と同じ美しさが見られることを願っています。