web-dev-qa-db-ja.com

標準ライブラリがプログラミング言語プリミティブではないのはなぜですか?

言語自体のプリミティブである同様の「関数」を使用するのではなく、stdlibなどの標準ライブラリ(C++、Java、Pythonなど、私が学んだすべてのプログラミング言語)になぜあるのかと考えていました。

32
user327143

@ Vincentの(+1)良い答え を少し拡張してみましょう:

コンパイラが関数呼び出しを一連の命令に単純に変換できないのはなぜですか?

少なくとも2つのメカニズムを介して実行できます。

  • 関数呼び出しのインライン化 —変換中に、コンパイラーは、ソースコード呼び出しを、実際に関数を呼び出す代わりに、インラインで直接その実装に置き換えることができます。それでも関数はどこかで定義された実装を持つ必要があり、それは標準ライブラリにあることができます。

  • 組み込み関数 —組み込み関数は、コンパイラーに通知された関数であり、必ずしもライブラリーで関数を見つける必要はありません。これらは通常、他の方法では実際にはアクセスできないハードウェア機能のために予約されており、非常に単純であるため、アセンブリ言語ライブラリ関数の呼び出しのオーバーヘッドでさえ高いと見なされます。 (コンパイラーは通常、その言語のソースコードを自動的にインライン化することのみ可能ですが、組み込み関数が組み込まれるアセンブリー関数はインライン化できません。)

それでもこれらが言われているとき、最良のオプションは、コンパイラーがソース言語の関数呼び出しをマシンコードの関数呼び出しに変換することです。再帰、仮想メソッド、および純粋なサイズは、インライン化が常に可能/実用的であるとは限らないいくつかの理由です。 (別の理由は、個別のコンパイル(オブジェクトモジュール)、個別のロードユニット(DLLなど)などのビルドの目的です)。

ほとんどの標準ライブラリー関数を組み込みにすることには実際の利点はありません(コンパイラーに多くの知識をハードコードして実際の利点をもたらさない)ので、マシンコード呼び出しが再び最も適切であることがよくあります。

Cは注目に値する言語であり、標準ライブラリ関数を支持するために他の明示的な言語ステートメントを間違いなく省略しました。ライブラリは以前から存在していましたが、この言語は、標準のライブラリ関数からより多くの作業を行うようになり、言語の文法での明示的なステートメントとしての作業は少なくなりました。 IOには独自の構文がさまざまなステートメントの形式で与えられることがよくありましたが、C文法ではIOステートメント、代わりに、標準ライブラリを使用してそれを提供するだけです。コンパイラはすでにその方法を知っているため、関数呼び出しを介してすべてアクセスできます。

31
Erik Eidt

これは単に、言語自体をできるだけ単純にするためです。ループの種類や関数にパラメーターを渡す方法など、言語の機能と、ほとんどのアプリケーションに必要な一般的な機能を区別する必要があります。

ライブラリは、多くのプログラマーに役立つ可能性がある関数であるため、共有可能な再利用可能なコードとして作成されます。標準ライブラリは、プログラマが通常必要とする非常に一般的な関数になるように設計されています。このようにして、プログラミング言語は幅広いプログラマーにすぐに役立ちます。ライブラリは、言語自体のコア機能を変更せずに更新および拡張できます。

69

他の答えがすでに言ったことに加えて、標準関数をライブラリに入れる懸念の分離

  • 言語を解析し、そのためのコードを生成するのはコンパイラーの仕事です。その言語で既に記述され、ライブラリーとして提供されているものを含めることは、コンパイラーの仕事ではありません。

  • 事実上すべてのプログラムに必要なcore機能を提供するのは、標準ライブラリ(常に暗黙的に利用可能なもの)の仕事です。役に立つかもしれないすべての関数を含むことは標準ライブラリの仕事ではありません。

  • オプションの標準ライブラリの役割は、多くのプログラムで実行できない補助機能を提供することですが、これは依然として非常に基本的であり、多くのアプリケーションが標準環境での出荷を保証するためにも不可欠です。これまでに作成されたすべての再利用可能なコードを含めることは、これらのオプションライブラリの仕事ではありません。

  • 有用な再利用可能な関数のコレクションを提供するのは、ユーザーライブラリの仕事です。これまでに作成されたすべてのコードを含めることは、ユーザーライブラリの仕事ではありません。

  • アプリケーションのソースコードの役割は、その1つのアプリケーションにのみ関連する残りのコードを提供することです。

万能のソフトウェアが必要な場合は、非常に複雑なものになります。複雑さを管理可能なレベルまで下げるには、モジュール化する必要があります。そして、partial実装を可能にするためにモジュール化する必要があります:

  • スレッド化ライブラリは、シングルコア組み込みコントローラーでは役に立ちません。この組み込みコントローラーの言語実装にpthreadライブラリーを含めないようにするのは、正しいことです。

  • 数学ライブラリは、FPUさえ備えていないマイクロコントローラでは役に立ちません。繰り返しになりますが、sin()のような関数を提供することを強制されないことで、そのマイクロコントローラーの言語の実装者にとって、人生はずっと簡単になります。

  • カーネルをプログラミングしているときは、コア標準ライブラリでさえ役に立たない。カーネルへのsyscallなしでwrite()を実装することはできません。また、printf()なしでwrite()を実装することもできません。カーネルプログラマーとして、write() syscallを提供するのはあなたの仕事です。

標準ライブラリからのそのような省略を許可しない言語は、多くのタスクに適していません。一般的ではない環境で言語を柔軟に使用できるようにするには、標準ライブラリに含まれているものに柔軟に対応できる必要があります。言語が標準ライブラリに依存するほど、実行環境での想定が増えるため、これらの前提条件を提供する環境への使用が制限されます。

もちろん、pythonおよびJavaのような高水準言語は、lotを作成できます)そして、Cのような低レベルの言語は、標準ライブラリで提供する機能がはるかに少なく、コア標準ライブラリをはるかに小さく保つことができます。そのため、動作するCコンパイラを見つけることができます。ほぼすべてのアーキテクチャで、pythonスクリプトを実行できない場合があります。

コンパイラと標準ライブラリが分離している大きな理由の1つは、2つの異なる目的に使用されているためです(両方が同じ言語仕様で定義されている場合でも):コンパイラは高レベルのコードを機械語命令に変換し、標準ライブラリは事前テスト済み一般的に必要な機能の実装。コンパイラー作成者は、他のソフトウェア開発者がするように、モジュール性を重視します。実際、初期のCコンパイラの中には、前処理、コンパイル、リンクのために、コンパイラをさらに別のプログラムに分割するものがあります。

このモジュール性には、多くの利点があります。

  • ほとんどの標準ライブラリコードはハードウェアに依存しないため、再利用できるため、新しいハードウェアプラットフォームをサポートするときに必要な作業量を最小限に抑えることができます。
  • 標準ライブラリの実装は、さまざまな方法で最適化できます(速度、スペース、リソース使用量など)。初期のコンピューティングシステムの多くは、1つのコンパイラしか利用できず、独立した標準ライブラリがあるため、開発者はニーズに合わせて実装を入れ替えることができました。
  • 標準ライブラリ機能は存在する必要さえありません。たとえば、ベアメタルCコードを記述する場合、フル機能のコンパイラーがありますが、標準ライブラリー機能のほとんどは存在せず、ファイルI/Oなどのいくつかのことも不可能です。コンパイラがこの機能を実装する必要がある場合、最も必要とする一部のプラットフォームでは、標準に準拠したCコンパイラを使用できませんでした。
  • 初期のシステムでは、コンパイラはハードウェアを設計した会社によって頻繁に開発されていました。標準ライブラリは、そのソフトウェアプラットフォームに固有の機能(システムコールなど)へのアクセスを必要とすることが多いため、OSベンダーによって頻繁に提供されていました。コンパイラの作成者がハードウェアとソフトウェアのさまざまな組み合わせをすべてサポートする必要があるのは現実的ではありませんでした(以前は、ハードウェアアーキテクチャとソフトウェアプラットフォームの両方で、はるかに多様性がありました)。
  • 高水準言語では、標準ライブラリを動的にロードされるライブラリとして実装できます。 1つの標準ライブラリ実装は、複数のコンパイラやプログラミング言語で使用できます。

歴史的に言えば(少なくともCの観点から)、元の標準化前バージョンの言語には標準ライブラリがまったくありませんでした。 OSベンダーやサードパーティは、よく使用される機能が満載のライブラリを提供することがよくありますが、実装が異なれば、実装内容も異なり、それらは互いにほとんど互換性がありませんでした。 Cが標準化されたとき、これらの異種の実装を調和させ、移植性を向上させるために、「標準ライブラリ」を定義しました。 C++のBoostライブラリと同様に、言語とは別に開発されたC標準ライブラリですが、後で言語仕様に統合されました。

15
bta

追加のコーナーケースの回答:知的財産管理

注目すべき例は 。NET FrameworkでのMath.Pow(double、double)の実装 であり、これはMicrosoftがIntelから購入したもので、フレームワークがオープンソースになったとしても非公開のままです。 (正確には、上記の場合はライブラリではなく内部呼び出しですが、考え方は成り立ちます。)言語自体から分離されたライブラリ(理論的には標準ライブラリのサブセット)は、言語の支持者に、 (サードパーティとの契約またはその他のIP関連の理由により)透明性を保つものと非開示にしておく必要のあるものの間の境界。

5
miroxlav

私自身も言語デザイナーとして、他のいくつかの答えをここにエコーしますが、言語を構築している誰かの目を通してそれを提供したいと思います。

APIにできる限りのものが追加されても、APIは終了しません。 APIは、APIから可能な限りすべてを取り出して終了します。

プログラミング言語は、いくつかの言語を使用して指定する必要があります。あなたの言語で書かれたプログラムの背後にある意味を伝えることができなければなりません。この言語は非常に書くのが難しく、うまく書くのがさらに難しいです。一般に、コンピューターではなく他の開発者、特に言語のコンパイラーまたはインタープリターを作成する開発者に意味を伝えるために使用される、非常に正確でよく構造化された形式の英語になる傾向があります。 C++ 11仕様[intro.multithread/14]の例を次に示します。

Mの値の計算Bに関して、アトミックオブジェクトMに対する目に見える副作用のシーケンスは、Mの変更順序における副作用の最大の連続したサブシーケンスであり、最初の副作用はBに関して可視です。 、そしてすべての副作用について、Bがその前に発生するというわけではありません。評価Bによって決定されるアトミックオブジェクトMの値は、何らかの操作によってBに関してMの可視シーケンスに格納された値になります。[注:値の副作用の可視シーケンスは、計算は、以下のコヒーレンス要件を考慮して一意です。 —エンドノート]

ブレック! C++ 11がマルチスレッドをどのように処理するかを理解した人なら誰でも、文言があまりに不透明である必要がある理由を理解できます。

これを、標準のライブラリセクションのstd::shared_ptr<T>::resetの定義と比較してください。

template <class Y> void reset(Y* p);

効果:shared_ptr(p).swap(*this)と同等

違いは何ですか?言語定義の部分では、作者は読者が言語プリミティブを理解していると想定することはできません。すべてを英語の散文で注意深く指定する必要があります。ライブラリ定義部分に到達したら、言語を使用して動作を指定できます。多くの場合、これははるかに簡単です。

原則として、スペックドキュメントの最初にプリミティブからスムーズに構築することができます。「言語プリミティブ」と「標準ライブラリ」機能。実際には、その線を描くことは非常に有益です。それは、それらを表現するように設計された言語を使用して、言語の最も複雑な部分(アルゴリズムを実装する必要がある部分など)の一部を書くことができるためです。

実際、ぼやけた線がいくつか見られます。

  • Javaでは、_Referenceの動作が非常に絡み合っているため、Java.lang.ref.Reference<T>は標準ライブラリクラスJava.lang.ref.WeakReference<T>Java.lang.ref.SoftReference<T>およびJava.lang.ref.PhantomReference<T>によってサブクラス化されるonly可能性がありますJava言語仕様。 "標準ライブラリ"クラスとして実装されているプロセスの部分にいくつかの制限を課すために必要でした。
  • C#には、デリゲートの概念をカプセル化するSystem.Delegateクラスがあります。その名前にもかかわらず、それはデリゲートではありません。また、派生クラスを作成できない(インスタンス化できない)抽象クラスでもあります。システムだけが、言語仕様に書かれた機能を通じてそれを行うことができます。
5
Cort Ammon

バグとデバッグ。

バグ:すべてのソフトウェアにはバグがあり、標準ライブラリにはバグがあり、コンパイラにはバグがあります。言語のユーザーとして、コンパイラーではなく標準ライブラリーにあるようなバグを見つけて回避するのははるかに簡単です。

デバッグ:標準ライブラリのスタックトレースを見て、何が問題になっているのかを理解するのがはるかに簡単です。そのスタックトレースにはコードが含まれているため、理解できます。もちろん、Digをより深く実行したり、組み込み関数をトレースしたりすることもできますが、日常的に使用する言語であれば、はるかに簡単です。

4
Pieter B

これはすばらしい質問です。

最先端

たとえば、C++標準では、コンパイラまたは標準ライブラリに実装する必要があるものを指定することはありません。単に実装を参照するだけです。たとえば、予約済みシンボルは、コンパイラー(組み込み関数)と標準ライブラリーの両方で同じ意味で定義されています。

それでも、私が知っているすべてのC++実装には、コンパイラによって提供される組み込みの最小限の数、および標準ライブラリによって提供される可能な限り多くの組み込みがあります。

したがって、標準ライブラリをコンパイラの組み込み機能として定義することは技術的には可能ですが、実際にはほとんど使用されていないようです。

どうして?

機能の一部を標準ライブラリからコンパイラに移動するというアイデアを考えてみましょう。

利点:

  • 診断の改善:組み込み関数は特別な場合があります。
  • パフォーマンスの向上:組み込み関数は特別な場合があります。

短所:

  • コンパイラの質量の増加:特殊なケースごとにコンパイラが複雑になります。複雑さにより、メンテナンスコストが増加し、バグが発生する可能性が高くなります。
  • 反復が遅い:機能の実装を変更するにはコンパイラー自体を変更する必要があるため、(std以外の)小さなライブラリーだけを作成して実験することは困難になります。
  • エントリーの基準が高くなる:何かを変更することがより高価/困難になるほど、人々が飛び込む可能性が低くなります。

これは、コンパイラへの移動は費用がかかるので、現在も将来も、したがって、大文字と小文字を区別する必要があります。一部の機能については、それが必要です(通常のコードとして作成することはできません)。ただし、それでも、最小限のgeneric部分を抽出して移動することはできます。コンパイラに渡し、標準ライブラリでそれらの上にビルドします。

4
Matthieu M.

これは、既存の回答への追加を意味します(コメントには長すぎます)。

標準ライブラリには、少なくとも2つの理由があります。

参入障壁

特定の言語機能がライブラリ関数にあり、それがどのように機能するか知りたい場合は、その関数のソースを読むだけで済みます。バグレポート/パッチ/プルリクエストを送信したい場合、修正とテストケースをコーディングすることは一般的にそれほど難しくありません。コンパイラー内にある場合は、内部を掘り下げる必要があります。それが同じ言語であっても(そうである必要がありますが、自尊心のあるコンパイラーはすべて自己ホスト型でなければなりません)コンパイラー・コードはアプリケーション・コードとは異なります。正しいファイルを見つけることさえも永遠にかかることがあります。

あなたがそのルートに行くなら、あなたは多くの潜在的な貢献者から自分を切り離しています。

ホットコード読み込み

多くの言語はこの機能をある程度提供していますが、ホットリロードを実行しているコードをホットリロードすることは非常に複雑になります。 SLがランタイムから分離されている場合は、再ロードできます。

1
Jared Smith

これは興味深い質問ですが、すでに多くの良い答えが与えられているので、完全なものを試すつもりはありません。

しかし、私が十分に注目していないと思う2つのこと:

まず、すべてが非常に明確ではないということです。物事を別の方法で行う理由があるので、それはスペクトルの少しです。例として、コンパイラーは標準ライブラリーとその機能についてよく知っています。例の例:Cの「Hello World」関数-printf-は、私が考えることができる最高のものです。これはライブラリー関数であり、プラットフォームに非常に依存しているため、並べ替える必要があります。しかし、その動作(定義された実装)は、不適切な呼び出しについてプログラマーに警告するためにコンパイラーによって認識される必要があります。これは特にきちんとしているわけではありませんが、妥協案として見られました。ちなみに、これは「なぜこのデザインなのか」という質問のほとんどに対する本当の答えです。多くの妥協があり、「当時は良いアイデアのように見えました」。その選択の支持者によってしばしば与えられるように、「これはそれを行うための明確な方法」または「見た目:スペクトルの遠端からの例」であるとは限らない。 (それらの回答は関連性がないことに注意してください)。

2つ目は、標準ライブラリがすべての標準ではないことを可能にすることです。言語が望ましい状況はたくさんありますが、通常それらに付随する標準ライブラリは実用的でも望ましいものでもありません。これは、非標準プラットフォームでのCなどのシステムプログラミング言語の場合が最も一般的です。たとえば、OSまたはスケジューラのないシステムの場合、スレッド化することはありません。

標準のライブラリモデル(およびその中でサポートされているスレッド)を使用すると、これをきれいに処理できます。コンパイラはほとんど同じで、適用するライブラリのビットと、削除できないものはすべて再利用できます。これがコンパイラに組み込まれていると、事態は複雑になります。

例えば:

  • 準拠コンパイラになることはできません。

  • 標準からの逸脱をどのように示しますか?通常、失敗する可能性のある形式のインポート/インクルード構文があることに注意してください。つまり、PythonのインポートまたはCのインクルードは、標準ライブラリモデルに欠けているものがある場合、問題を簡単に指摘します。

「ライブラリ」機能を調整または拡張する場合にも、同様の問題が発生します。これはあなたが思っているよりもはるかに一般的です。スレッドに固執するだけです。ウィンドウ、Linux、およびいくつかのエキゾチックなネットワーク処理ユニットはすべて、スレッドの実行方法がまったく異なります。 Linux/Windowsのビットはかなり静的で、同じAPIを使用できる場合がありますが、NPUのものは曜日とAPIによって変化します。この種のものを分割する方法がなかった場合、サポートする必要があるビットをすばやく決定できるようになると、コンパイラーはすぐに逸脱します。

1
drjpizzle