私が学んだすべてのプログラミング言語(C ++、Java、Pythonなど)に、言語自体のプリミティブである同様の「関数」ではなく、stdlibのような標準ライブラリがある理由を考えていました。
私が学んだすべてのプログラミング言語(C ++、Java、Pythonなど)に、言語自体のプリミティブである同様の「関数」ではなく、stdlibのような標準ライブラリがある理由を考えていました。
回答:
@Vincent の(+1)の良い答えを少し拡大させてください。
コンパイラーが関数呼び出しを一連の命令に単純に変換できなかったのはなぜですか?
少なくとも2つのメカニズムを介して実行できます。
関数呼び出しのインライン化 —翻訳中、コンパイラーは、関数を実際に呼び出す代わりに、ソースコード呼び出しをその実装に直接インラインで置き換えることができます。それでも、関数はどこかで定義された実装を持っている必要があり、それは標準ライブラリにあります。
組み込み関数 —組み込み関数は、必ずしもライブラリ内で関数を見つけることなくコンパイラーに通知された関数です。これらは通常、他の方法では実際にはアクセスできないハードウェア機能のために予約されており、アセンブリ言語ライブラリ関数の呼び出しのオーバーヘッドでさえ高いと見なされるほど単純です。(コンパイラは通常、その言語でソースコードを自動的にインライン化することしかできませんが、組み込みメカニズムが入るアセンブリ関数はインライン化できません。)
それでもこれらは言われていますが、最良のオプションは、コンパイラがソース言語の関数呼び出しをマシンコードの関数呼び出しに変換することです。再帰、仮想メソッド、および実際のサイズは、インライン化が常に可能/実用的ではないいくつかの理由です。(別の理由は、個別のコンパイル(オブジェクトモジュール)、個別のロードユニット(DLLなど)などのビルドの意図です)。
ほとんどの標準ライブラリ関数を組み込み関数にすることには実際の利点もありません(実際の利点がないためにコンパイラに多くの知識をハードコーディングすることになります)。
Cは、標準ライブラリ関数を支持して他の明示的な言語ステートメントをほぼ間違いなく省略した注目すべき言語です。ライブラリはすでに存在していましたが、この言語は、標準のライブラリ関数からより多くの作業を行うようになり、言語の文法における明示的なステートメントとしてではなくなりました。たとえば、他の言語のIOには、さまざまなステートメントの形で独自の構文が頻繁に与えられますが、C文法ではIOステートメントを定義せず、代わりにその標準ライブラリを使用して、すべてを提供します。コンパイラはすでにその方法を知っています。
これは、言語自体をできるだけ単純にするためです。ループのタイプや関数にパラメーターを渡す方法など、言語の機能と、ほとんどのアプリケーションに必要な共通機能を区別する必要があります。
ライブラリは、多くのプログラマにとって有用な関数であるため、共有可能な再利用可能なコードとして作成されます。標準ライブラリは、プログラマが通常必要とする非常に一般的な機能になるように設計されています。このように、プログラミング言語は、幅広いプログラマーにとってすぐに役立ちます。ライブラリは、言語自体のコア機能を変更せずに更新および拡張できます。
PHP
例として、その広大な言語機能と言語自体の間にほとんど違いはありません。
include
、require
およびrequire_once
、(構造化プログラミング)、例外、「エラー値」の別のシステム、複雑弱い型付け規則、複雑な演算子の優先順位のルール、およびオンとでしばらく/ /用の場合。これを、Smalltalk、Scheme、Prolog、Forthなどの単純さと比較してください;)
他の答えがすでに言ったことに加えて、標準関数をライブラリに入れることは懸念の分離です:
言語を解析し、そのためのコードを生成するのはコンパイラの仕事です。すでにその言語で記述でき、ライブラリとして提供できるものを含めるのは、コンパイラの仕事ではありません。
事実上すべてのプログラムに必要なコア機能を提供するのは、標準ライブラリ(常に暗黙的に利用可能なもの)の仕事です。役に立つかもしれないすべての機能を含むのは、標準ライブラリの仕事ではありません。
オプションの標準ライブラリの仕事は、多くのプログラムがなしで行うことができる補助機能を提供することですが、それは依然として非常に基本的であり、多くのアプリケーションが標準環境での出荷を保証するためにも不可欠です。これまでに作成されたすべての再利用可能なコードを含めるのは、これらのオプションライブラリの仕事ではありません。
有用な再利用可能な関数のコレクションを提供するのは、ユーザーライブラリの仕事です。これまでに作成されたすべてのコードを含めるのは、ユーザーライブラリの仕事ではありません。
アプリケーションのソースコードの仕事は、実際にその1つのアプリケーションにのみ関連する残りのコードを提供することです。
万能のソフトウェアが必要な場合は、非常に複雑なものになります。複雑さを管理可能なレベルに下げるには、モジュール化する必要があります。そして、部分的な実装を可能にするためにモジュール化する必要があります。
スレッドライブラリは、シングルコアの組み込みコントローラーでは価値がありません。この組み込みコントローラの言語実装にpthread
ライブラリを含めないようにすることは、正しいことです。
数学ライブラリは、FPUを備えていないマイクロコントローラーでは価値がありません。繰り返しにsin()
なりますが、そのようなマイクロコントローラーの言語の実装者にとっては、次のような機能を提供することを余儀なくされることは非常に簡単です。
コア標準ライブラリでさえ、カーネルをプログラミングしているときには価値がありません。あなたは実装できないwrite()
カーネルにシステムコールせずに、あなたが実装することはできませんprintf()
なしwrite()
。カーネルプログラマーとして、write()
システムコールを提供するのはあなたの仕事です。
標準ライブラリからそのような省略を考慮しない言語は単に多くのタスクに適していません。まれな環境で言語を柔軟に使用できるようにする場合は、標準ライブラリが含まれる言語で柔軟でなければなりません。言語が標準ライブラリに依存するほど、その実行環境に対する前提が増え、そのため、これらの前提条件を提供する環境への使用が制限されます。
もちろん、pythonやjavaのような高レベル言語は、環境について多くの仮定を立てることができます。そして、彼らは標準ライブラリに多くの多くのものを含める傾向があります。Cなどの低レベル言語は、標準ライブラリで提供されるものがはるかに少なく、コア標準ライブラリをはるかに小さく保ちます。事実上、どのアーキテクチャでも動作するCコンパイラを見つけることができますが、Pythonスクリプトを実行できない場合があります。
コンパイラーと標準ライブラリーが分離されている大きな理由の1つは、2つの異なる目的を果たしているためです(両方とも同じ言語仕様で定義されている場合でも):コンパイラーは高レベルのコードをマシン命令に変換し、標準ライブラリーは事前テスト済みを提供します一般的に必要な機能の実装。コンパイラライターは、他のソフトウェア開発者と同じようにモジュール性を重視します。実際、初期のCコンパイラの一部は、コンパイラを前処理、コンパイル、およびリンクのために個別のプログラムにさらに分割しました。
このモジュール性には、多くの利点があります。
歴史的に(少なくともCの観点から)、言語の元の標準化前バージョンには標準ライブラリがまったくありませんでした。OSベンダーとサードパーティは、一般的に使用される機能で満たされたライブラリを提供することがよくありますが、実装にはさまざまなものが含まれており、互いにほとんど互換性がありませんでした。Cが標準化されたとき、これらの異種の実装を調和させ、移植性を改善するために、「標準ライブラリ」を定義しました。C標準ライブラリは、BoostライブラリがC ++用に持っているように、言語とは別に開発されましたが、後に言語仕様に統合されました。
追加のコーナーケース回答:知的財産管理
注目すべき例は、MicrosoftがIntelから購入した.NET FrameworkのMath.Pow(double、double)の実装であり、フレームワークがオープンソースになったとしても非公開のままです。(正確には、上記の場合、それはライブラリではなく内部呼び出しですが、アイデアは保持されます。)言語自体(理論的には標準ライブラリのサブセット)から分離されたライブラリは、言語支援者に描画の柔軟性を与えることができます透明性を維持するものと、非公開のままにする必要があるものとの間の境界線(第三者との契約またはその他のIP関連の理由による)。
Math.Pow
は、購入やIntel に関する情報は一切記載されておらず、関数の実装のソースコードを読んでいる人々について説明しています。
これは素晴らしい質問です!
C ++標準は、例えば、コンパイラや標準ライブラリに実装すべきかを指定することはありません:それはちょうどを指し実装。たとえば、予約済みシンボルは、コンパイラー(組み込み関数として)と標準ライブラリーの両方で交換可能に定義されます。
しかし、私が知っているすべてのC ++実装には、コンパイラーによって提供される組み込み関数の最小限の数と、標準ライブラリによって提供されるできるだけ多くの組み込み関数があります。
したがって、標準ライブラリをコンパイラの組み込み機能として定義することは技術的には可能ですが、実際にはめったに使用されないようです。
機能の一部を標準ライブラリからコンパイラに移動するというアイデアを考えてみましょう。
利点:
短所:
std
、実験のために小さなライブラリ(以外)を作成するのが難しくなります。これは、何かをコンパイラーに移動することは、現在も将来も費用がかかることを意味するため、しっかりとしたケースが必要です。一部の機能には必要なものがあります(通常のコードとして作成することはできません)が、それでも最小限の汎用的な部分を抽出してコンパイラーに移動し、標準ライブラリでそれらの上にビルドすることは代償です。
私自身、言語デザイナーとして、ここで他の答えのいくつかをエコーしたいのですが、言語を構築している誰かの目を通してそれを提供したいと思います。
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.lang.ref.Reference<T>
ことのみ標準ライブラリクラスによってサブクラス化するjava.lang.ref.WeakReference<T>
java.lang.ref.SoftReference<T>
とjava.lang.ref.PhantomReference<T>
の挙動があるためReference
非常に深く、彼らは「標準ライブラリ」クラスとして実装そのプロセスの部分にいくつかの制限を置くために必要なことをJava言語仕様に絡み合っています。これは、既存の回答に追加することを意味します(コメントするには長すぎます)。
標準ライブラリには、少なくとも2つの他の理由があります。
特定の言語機能がライブラリ関数にあり、その機能を知りたい場合は、その関数のソースを読むだけです。バグレポート/パッチ/プルリクエストを提出したい場合、一般的に修正とテストケースをコーディングするのはそれほど難しくありません。コンパイラー内にある場合、内部を掘り下げなければなりません。たとえ同じ言語であるとしても(そして、そうであるべきであり、自尊心のあるコンパイラーは自己ホストされるべきです)、コンパイラー・コードはアプリケーション・コードのようなものではありません。正しいファイルを見つけるまでに時間がかかる場合があります。
そのルートに行けば、多くの潜在的な貢献者から自分を切り離すことになります。
多くの言語はこの機能をある程度提供していますが、ホットリロードを実行しているコードをホットリロードするのは非常に複雑です。SLがランタイムから分離されている場合、再ロードできます。
これは興味深い質問ですが、すでに多くの良い答えが与えられているので、完全なものを試みるつもりはありません。
しかし、十分に注目されていないと思われる2つのこと:
第一に、全体が非常に明確ではないということです。物事を異なる方法で行う理由があるため、それはちょっとしたスペクトルです。例として、コンパイラは標準ライブラリとその機能についてよく知っています。例の例:Cの「Hello World」関数-printf-は、私が考えることができる最高のものです。これはライブラリ関数であり、プラットフォームに非常に依存しているので、そうでなければなりません。しかし、プログラマーに不正な呼び出しについて警告するために、その動作(定義された実装)をコンパイラーに知らせる必要があります。これは特にきちんとしたものではありませんが、良い妥協案と見なされていました。ちなみに、これはほとんどの「なぜこのデザイン」の質問に対する本当の答えです。多くの妥協と「当時は良いアイデアのように思えました」。必ずしも「これが明確な方法」または「
2つ目は、標準ライブラリをすべての標準にしないことです。言語が望ましい多くの状況がありますが、通常それらに付随する標準ライブラリは実用的でも望ましいものでもありません。これは、非標準プラットフォーム上のCなどのシステムプログラミング言語で最もよく見られます。たとえば、OSまたはスケジューラのないシステムがある場合:スレッドは使用しません。
標準ライブラリモデル(およびその中でサポートされているスレッド)を使用すると、これをきれいに処理できます。コンパイラはほぼ同じで、適用するライブラリの一部と、削除できないものを再利用できます。これがコンパイラに組み込まれると、事態は面倒になり始めます。
例えば:
準拠したコンパイラになることはできません。
標準からの逸脱をどのように示しますか。通常、失敗する可能性のあるインポート/インクルード構文の形式があることに注意してください。つまり、標準ライブラリモデルに何かが欠けている場合、問題を簡単に指し示すpythonsのインポートまたはCのインクルードです。
「ライブラリ」機能を調整または拡張する場合にも、同様の問題が適用されます。これは、思っているよりもはるかに一般的です。スレッド化に固執するだけです。Windows、Linux、および一部のエキゾチックなネットワーク処理ユニットはすべて、まったく異なるスレッド化を行います。linux / windowsビットはかなり静的であり、同一のAPIを使用できる可能性がありますが、NPUのものは曜日とAPIによって変わります。コンパイラーは、この種のものを分割する方法がなければ、サポートする必要のあるビットを非常に迅速に決定するので、人々はすぐに逸脱します。