Programming-language

なぜ null はそのプログラミング言語にあるのか(その1 - ”無”の無い世界)

More than 3 years have passed since last update.


前提

TaPL 読みながら Haskell/Denotational semantics 見て、自分なりに納得した己が解釈を文芸的に綴っていくだけの内容です


序: void, null, undefined, (), [], ⊥

どんな言語でも「空っぽ」なり「無い」なり「未定義」なりを意味する値が用意されており、型付けが動的だろうと静的だろうとこれらに対してだけはヤケに自由度の高い用法を許されています。


Haskell の undefined と ( ) 型

Haskellであっても、以下のコードは「正しく型づけされ、正しく動作する」としてコンパイルが通ります

fib :: Integer -> Integer

fib 0 = 1
fib 1 = 1
fib n
| n > 1 = fib (n - 2) + fib (n - 1)
| otherwise = undefined -- (n < 0)

なお、この時の undefined はこういう型で存在しています

undefined :: a -- どんな型であっても良い

ついで、Haskell には「何も返さない事を意味する型」として()(Unit型と読む)モノがあります。mainの型がIO ()です。putStrLn :: String -> IO ()であったり、IOが関わる処理では割と頻出します


※この内容はあくまでも『考察』です※

javascirptのグローバル変数undefined,null、RubyのNilClass、Objective-Cのnil、Javaのnull…様々な言語に其々の「空」や「無」や「未定義」を意味する値が用意されていますが、そもそも何でこうした値を用意しなくてはいけなかったのか? という点について振り返ってみます


null とは

往々にして、各自のプログラミング遍歴で null に持つ「意味」は異なってくると思います



  • null「空」/「無」を意味する


  • null「未定義値」を意味する


  • nullインスタンスが無かった時のアレ

  • x.do(some) unless x でブロック進んじゃってグエーってなるヤツ

  • == ! の結果でfalsyとして扱われてコーディングややこしくさせてるヤツ

この記事の主題は「なんで、こんなに厄介な存在が要るん?」について、それなりに「しょうがにゃいにゃあ」と思える解釈を綴るものです


「空」/「無」/「未定義」の無い世界

はじめに、C言語で考えてみます。尤も、この手の話だったらALGOLとかCOBOLやPascalの話もしないといけないのでしょうけれど、それらへは大きく語れる知見を持ってはいないので…


true/false ? no, it's "0 or not."

extern int

doSomething()
{
/* ... */
}

int
main()
{
while(0) {
/* ここは絶対に実行されない */
doSomething();
}
return 0;
}

C言語そのものにはbool型というのが提供されておらず、条件分岐は 00以外を真偽値として扱います。(C99からは #include <stdbool.h> すれば一応bool型を使えますが、それでもif(){}for(;;){}while(){}にはそのままintのリテラルが使えます)

enum による列挙にしても、実体は整数ですし、基本的に 整数の値をどのように解釈するか というハンドリングが求められます。TaPL では「C言語の静的型検査の型システムは安全ではない」と述べられていますが、次に続く内容でその一端を触れる事が出来ます


void* NULL

C言語仕様では、mallocでメモリ確保に失敗した時に返すヌルポインタは処理系に応じてその判断が要る為、stdlib.h から NULL を利用し、同値判定させるコトで「失敗」を判定させるのが慣習となっています

(NULLはC89から整数定数0(void*) 0で表現されるものと定義されていますが、コンパイル後にどうなるかは実装依存とされています)

#include <stdlib.h>


/* ... */
int* ptr = (int*)malloc(sizeof(int) * len);
if (NULL == ptr) {
exit(EXIT_FAILURE);
}
/* ... */
free(ptr);
ptr = NULL;
/* ... */


int is_empty(void* ptr) への実装自由度の問題

なお、NULLに実値のあるオブジェクトのポインタや関数のポインタを割り当ててはいけないというのがあり、それは安心できるのですが、「それが空っぽであるかどうか」は、プログラマの設計に裁量が求められます


  1. 構造体はグローバル領域で宣言されているが、中身は空っぽ

  2. 構造体はスタック領域の中で宣言されており、中身は空っぽ

  3. 構造体としての変数のポインタがNULLである(ヒープ領域にアドレス確保してない)

  4. 構造体としての変数のポインタがNULLである(アドレス確保に失敗した!)

  5. 変数のポインタはヒープアドレスには存在するが


    • a.メンバーのポインタは未だNULL(メンバーへのmalloc忘れてる)

    • b.メンバーのアドレスも確保されているが、値は未だ代入されていない



  6. アドレスはまだヒープ領域から参照できるが、単にfree()の後にNULL代入するの忘れた(ヤバイ)

この中で確実に「不手際」なのは 6. くらいですが、それ以外については「ライブラリ利用者がどう介入したか?」によって「ヘマ」「設計ミス」「バグ」か分かれます。転じて、コードの読み手からは実態を推し量りにくいとも言えます。


書き手と読み手に強いるモノ

というわけで、言語設計上で「空」「無」を意味するモノが無いという事は、プログラマに「何を以ってして空とするか」を自己責任で決めさせる事を意味します

そして、「空」だけでなく「意図的に、その時点では”無い”状態である」と、「この時点では未定義としか言い様のないモノである(しかし宣言はしないといけない)」ことを、ライブラリを読み、利用する人へも理解を要求する事になります

ライブラリのソースにコメントなどがあれば、それを確認する事で設計者の意図を組みとれる術はありますが、既にダイナミックリンクライブラリとしてバイナリ化されてしまったモノとなると困難なはずです

そこで利用する側がコンパイル時や実行時に知る事が出来る、利用上の注意点である、メタな情報としての「型」と「値」のアイデアが活きてきます


次回 「”無”が有る世界」

モチベーションと反響があったら続き書きます。

Lisp の '(), Objective-C の nil, RDB(SQL)のNULL, Python の None 等々の話から、「無い」を意味する値の意味 について


見識違いへのツッコミ等

ありがとうございます、ご教授願います