標準ライブラリがプログラミング言語プリミティブではないのはなぜですか?[閉まっている]


30

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


4
「コンパイラーが関数呼び出しを一連の命令に単純に変換できなかったのはなぜですか」これは、コンパイラが行うおおよそのことです。標準ライブラリであろうとなかろうと(Ok、Pythonは途中で、JavaからJVMバイトコードまで、同様の概念)。標準ライブラリは、実際にはコードのコンパイルとは関係ありません->命令。
デリオス

25
@Deliothシモンは、言語$ LANGの標準ライブラリのすべてが、その言語の原始的な構造/関数ではない理由を尋ねていると思います。プログラミング言語を初めて使う人にとっては合理的な質問だと思います:)
Andres F.

33
通常、標準ライブラリは、動作するプログラミング言語と人々が使用する有用なプログラミング言語との間のギャップを埋めます。
テラスティン

6
Pythonの標準ライブラリの重要な部分は、実際にはCで記述され、すでにコンパイルされています。
ElmoVanKielmo

1
対照的に、BASICのほとんどの実装では、すべてが言語の一部であり、ライブラリもまったくサポートされていません(いくつかの実装では、機械語ルーチンを呼び出す機能を保存します)。
ユーロミチェリ

回答:


32

@Vincent の(+1)の良い答えを少し拡大させてください。

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

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

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

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

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

ほとんどの標準ライブラリ関数を組み込み関数にすることには実際の利点もありません(実際の利点がないためにコンパイラに多くの知識をハードコーディングすることになります)。

Cは、標準ライブラリ関数を支持して他の明示的な言語ステートメントをほぼ間違いなく省略した注目すべき言語です。ライブラリはすでに存在していましたが、この言語は、標準のライブラリ関数からより多くの作業を行うようになり、言語の文法における明示的なステートメントとしてではなくなりました。たとえば、他の言語のIOには、さまざまなステートメントの形で独自の構文が頻繁に与えられますが、C文法ではIOステートメントを定義せず、代わりにその標準ライブラリを使用して、すべてを提供します。コンパイラはすでにその方法を知っています。


3
いい答えだ。Cでこの決定が行われた理由をいくつか追加する必要があります。正​​しく覚えていれば、主な理由は、実際には多くの異なるハードウェアアーキテクチャ用のCコンパイラを作成するのが簡単になったからです。
Doc Brown

10
@DocBrown 1975年までに、プログラミング言語開発の分野(ALGOL-68、だれでも?)に十分な例があり、すべてを言語に焼き付けようとすると、言語仕様の仕上げと言語実装の作成。
Joker_vD

5
同様の例は、Pythonで行ったことですprint。2.xでは、独自の特別な文法を持つステートメントでしたが、3.xでは、別の関数呼び出しになりました。公式の説明については、PEP 3105をご覧ください。
dan04

1
@DocBrown、移植性はほぼ間違いなく理由ではありませんでした。UnixとCが作成されたとき、ケントンプソンは失敗したMulticsプロジェクトからどのような概念を回収できるのかと疑問に思ったため、スペアとなるPDP-7のマシン1つだけのために設計および構築されました。Cはまた、1つの理由で作成されました。Unixを(再)実装するための高レベル言語を使用するためです。これらは基本的にソフトウェア設計の実験であり、商用のマルチプラットフォームOSおよび言語に対する真剣な試みではありません。たとえば、bell-labs.com / usr / dmr / www / chist.htmlを参照してください。
ユーロミチェリ

@EuroMicelli:そこには矛盾はありません。そして、あなたの参考文献には、移植性が重要になった時期に関する多くの詳細が含まれており、実際にはCおよびUnix開発の初期の頃のものでした。私はここでしか推測できませんが、Cの発明者が言語を意図的に小さくしていなかったなら、多くの異なるアーキテクチャにこれを迅速かつうまく移植できた可能性は非常に低いと思います。
Doc Brown

70

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

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


3
常にではない。PHP例として、その広大な言語機能と言語自体の間にほとんど違いはありません。
Vahid Amiri

15
PHPを単純な言語の例とは
見なし

3
@DrBreakalot PHPは非常にシンプルな言語です。それは一貫したデザインを持っていると言うことではありませんが、それは別の問題です。
モニカとの軽さレース

19
@LightnessRacesinOrbit PHPを「シンプル」とは呼びません。クラスベースのオブジェクトシステム、個別の「プリミティブ値」セット、スタンドアロン関数、オブジェクトシステム上に構築されたファーストクラスクロージャ、名前空間メカニズム、さまざまなものがあります。 「静的」と呼ばれる概念、文並びに式、includerequireおよびrequire_once、(構造化プログラミング)、例外、「エラー値」の別のシステム、複雑弱い型付け規則、複雑な演算子の優先順位のルール、およびオンとでしばらく/ /用の場合。これを、Smalltalk、Scheme、Prolog、Forthなどの単純さと比較してください;)
Warbo

3
この答えで示唆されているが明示的には述べられていない主な理由は、言語をできるだけシンプルに保つことにより、他のプラットフォームでの実装がはるかに簡単になることです。通常、標準ライブラリは言語自体記述されているため簡単に移植できます。
BlueRaja-ダニー・フルーゴフト

34

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

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

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

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

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

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

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

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

  • 数学ライブラリは、FPUを備えていないマイクロコントローラーでは価値がありません。繰り返しにsin()なりますが、そのようなマイクロコントローラーの言語の実装者にとっては、次のような機能を提供することを余儀なくされることは非常に簡単です。

  • コア標準ライブラリでさえ、カーネルをプログラミングしているときには価値がありません。あなたは実装できないwrite()カーネルにシステムコールせずに、あなたが実装することはできませんprintf()なしwrite()。カーネルプログラマーとして、write()システムコールを提供するのはあなたの仕事です。

標準ライブラリからそのような省略を考慮しない言語は単に多くのタスクに適していません。まれな環境で言語を柔軟に使用できるようにする場合は、標準ライブラリが含まれる言語で柔軟でなければなりません。言語が標準ライブラリに依存するほど、その実行環境に対する前提が増え、そのため、これらの前提条件を提供する環境への使用が制限されます。

もちろん、pythonやjavaのような高レベル言語は、環境について多くの仮定を立てることができます。そして、彼らは標準ライブラリに多くの多くのものを含める傾向があります。Cなどの低レベル言語は、標準ライブラリで提供されるものがはるかに少なく、コア標準ライブラリをはるかに小さく保ちます。事実上、どのアーキテクチャでも動作するCコンパイラを見つけることができますが、Pythonスクリプトを実行できない場合があります。


16

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

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

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

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


6

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

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


これは紛らわしいです。リンク先のページにMath.Powは、購入やIntel に関する情報は一切記載されておらず、関数の実装のソースコードを読んでいる人々について説明しています。
モニカとの軽さレース

@LightnessRacesinOrbit –ふむ、まだそこに見えます( "intel"を検索しているとき)。また、最新のソースコードへの参照(最新のコメント)と、公開されているが、元の実装がまだ開示されていない理由を示す複雑でコメント付きの非効率性がある代替実装(2番目の回答)も見つけることができます。本当に効率的な実装には、CPUレベルの多くの詳細についての深い知識が必要になる場合がありますが、これらは必ずしもパブリックドメインで利用できるとは限りません。
ミロクスラフ

5

バグとデバッグ。

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

デバッグ:標準ライブラリのスタックトレースを見るのがずっと簡単になり、何が間違っているのかをある程度理解できます。そのスタックトレースには理解できるコードがあるからです。もちろん、より深く掘り下げることもできますし、組み込み関数をトレースすることもできますが、毎日使用する言語であれば、ずっと簡単です。


5

これは素晴らしい質問です!

最先端

C ++標準は、例えば、コンパイラや標準ライブラリに実装すべきかを指定することはありません:それはちょうどを指し実装。たとえば、予約済みシンボルは、コンパイラー(組み込み関数として)と標準ライブラリーの両方で交換可能に定義されます。

しかし、私が知っているすべてのC ++実装には、コンパイラーによって提供される組み込み関数の最小限の数と、標準ライブラリによって提供されるできるだけ多くの組み込み関数があります。

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

どうして?

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

利点:

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

短所:

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

これは、何かをコンパイラーに移動することは、現在も将来も費用かかることを意味するため、しっかりとしたケースが必要です。一部の機能には必要なものがあります(通常のコードとして作成することはできません)が、それでも最小限の汎用的な部分を抽出してコンパイラーに移動し、標準ライブラリでそれらの上にビルドすることは代償です。


5

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

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では、java.lang.ref.Reference<T>ことのみ標準ライブラリクラスによってサブクラス化するjava.lang.ref.WeakReference<T> java.lang.ref.SoftReference<T>java.lang.ref.PhantomReference<T>の挙動があるためReference非常に深く、彼らは「標準ライブラリ」クラスとして実装そのプロセスの部分にいくつかの制限を置くために必要なことをJava言語仕様に絡み合っています。
  • C#には、デリゲートの概念をカプセル化するSystem.Delegateクラスがあります。その名前にもかかわらず、代理人ではありません。また、派生クラスを作成できない抽象クラス(インスタンス化できません)です。言語仕様に書かれた機能を介してそれを行うことができるのはシステムのみです。

2

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

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

参入障壁

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

そのルートに行けば、多くの潜在的な貢献者から自分を切り離すことになります。

ホットコードの読み込み

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


3
「自尊心のあるコンパイラーは自己ホスト型でなければなりません」-まったくそうではありません。たとえば、LLVMのバージョンをC、C ++、Objective-C、Swift、Fortranなどで記述して、これらすべての言語をコンパイルしても意味がありません。
gnasher729

@ gnasher729は、(CLRのような他の多言語ターゲットと一緒に)特別なケースではありませんか?
ジャレッドスミス

@JaredSmith私はそれが今では一般的なケースであり、特別なものではないと言うでしょう。モノリシックアプリケーションとして「コンパイラ」を書く人はいません。代わりにコンパイラシステムを作成します。完全なコンパイラの機能のほとんどは、コンパイルされている特定の言語から完全に独立しており、言語に依存する部分の多くは、言語ごとに異なるコードを記述するのではなく、言語の文法を定義する異なるデータを提供することで実行できますコンパイルしたい。
alephzero

2

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

しかし、十分に注目されていないと思われる2つのこと:

第一に、全体が非常に明確ではないということです。物事を異なる方法で行う理由があるため、それはちょっとしたスペクトルです。例として、コンパイラは標準ライブラリとその機能についてよく知っています。例の例:Cの「Hello World」関数-printf-は、私が考えることができる最高のものです。これはライブラリ関数であり、プラットフォームに非常に依存しているので、そうでなければなりません。しかし、プログラマーに不正な呼び出しについて警告するために、その動作(定義された実装)をコンパイラーに知らせる必要があります。これは特にきちんとしたものではありませんが、良い妥協案と見なされていました。ちなみに、これはほとんどの「なぜこのデザイン」の質問に対する本当の答えです。多くの妥協と「当時は良いアイデアのように思えました」。必ずしも「これが明確な方法」または「

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

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

例えば:

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

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

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

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