web-dev-qa-db-ja.com

「blubパラドックス」とc ++

私はここの記事を読んでいました: http://www.paulgraham.com/avg.html そして「blub paradox」に関する部分は特に興味深いものでした。主にc ++でコーディングしているが、他の言語(主にHaskell)に触れている人として、c ++で複製するのが難しいこれらの言語のいくつかの便利な点を知っています。質問は主にc ++と他の言語の両方に習熟している人を対象としていますが、c ++だけで記述している場合、概念化または実装が難しい言語で使用する強力な言語機能またはイディオムはありますか?

特に、この引用は私の注意を引いた:

帰納法により、さまざまな言語間の力のすべての違いを見ることができる唯一のプログラマーは、最も強力なものを理解しているプログラマーです。 (これはおそらく、エリックレイモンドがLISPについてあなたをより優れたプログラマーにすることを意味したものです。)他の人の意見を信用することはできません。ブラブのパラドックスが原因です。彼らは、使用する言語に満足しています。彼らがプログラムについて考える方法。

私がc ++を使用することにより「Blub」プログラマーと同等であることが判明した場合、これは次の質問を投げかけます:あなたが他の言語で遭遇し​​た、概念化するのが難しいと思ったであろう有用な概念や技術はありますかc ++で書いているか「考えている」?

たとえば、プロローグやマーキュリーなどの言語で見られるロジックプログラミングパラダイムは、キャスターライブラリを使用してc ++で実装できますが、最終的には概念的にはプロローグコードの観点から考え、これを使用する場合は同等のc ++に変換していることがわかります。私のプログラミング知識を広げる方法として、私がc ++開発者として知らないかもしれない他の言語でより効率的に表現される有用で強力なイディオムの他の同様の例があるかどうかを調べようとしています。頭に浮かぶもう1つの例は、LISPのマクロシステムです。プログラム内からプログラムコードを生成すると、いくつかの問題に対して多くの利点があるようです。これはc ++内から実装して考えるのが難しいようです。

この質問は、「c ++ vs LISP」の議論や、あらゆる種類の言語戦争型の議論を意図したものではありません。このような質問をすることは、私が知らないことについて知らないことについて知ることができるのを見ることができる唯一の方法です。

37
shuttle87

まあ、あなたはHaskellに言及したので:

  1. パターンマッチング。パターンマッチングの方が読み書きがはるかに簡単だと思います。マップの定義を検討し、パターンマッチングのない言語でそれがどのように実装されるかを考えてください。

    map :: (a -> b) -> [a] -> [b]
    map f [] = []
    map f (x:xs) = f x : map f xs
    
  2. 型システム。時には痛みを伴うこともありますが、非常に役立ちます。あなたはそれを本当に理解し、それがいくつのバグを捕らえるかを理解するためにそれを使ってプログラムしなければなりません。また、参照の透明性は素晴らしいです。 Haskellでプログラミングをしばらくしてから明らかになるのは、命令型言語で状態を管理することによっていくつのバグが引き起こされるかです。

  3. 一般的な関数型プログラミング。反復の代わりにマップとフォールドを使用します。再帰。より高いレベルで考えることです。

  4. 遅延評価。繰り返しになりますが、より高いレベルで考え、システムに評価を任せることです。

  5. Cabal、パッケージ、およびモジュール。 Cabalダウンロードパッケージを用意しておくと、ソースコードを見つけたり、メイクファイルを作成したりするよりもはるかに便利です。特定の名前だけをインポートできることは、基本的にすべてのソースファイルを一緒にダンプしてコンパイルするよりもはるかに優れています。

16
Joel Burget

メモ化!

C++で書いてみてください。 C++ 0xではではありません。

面倒すぎる?さて、それを試してみてくださいwithC++ 0x。

Dでこの4行(または5行、何でも:P)compile-timeバージョンに勝てるかどうかを確認してください。

auto memoize(alias Fn, T...)(T args) {
    auto key = Tuple(args);                               //Key is all the args
    static typeof(Fn(args))[typeof(key)] cache;           //Hashtable!
    return key in cache ? cache[key] : (cache[key] = Fn(args));
}

それを呼び出すためにあなたがする必要があるのは次のようなものです:

int fib(int n) { return n > 1 ? memoize!(fib)(n - 1) + memoize!(fib)(n - 2) : 1;}
fib(60);

また、Schemeでも同様のことを試すことができますが、実行時に発生し、ここでの検索はハッシュではなく線形であるため(そして、Schemeであるため)、少し遅くなります。

(define (memoize f)
    (let ((table (list)))
        (lambda args
            (cdr
                (or (assoc args table)
                    (let ((entry (cons args (apply f args))))
                        (set! table (cons entry table))
                        entry))))))
(define (fib n)
        (if (<= n 1)
            1
            (+ (fib (1- n))
                (fib (- n 2)))))))
(set! fib (memoize fib))
7
user541686

C++はマルチパラダイム言語です。つまり、多くの考え方をサポートしようとします。関数型プログラミングの場合のように、C++機能は、他の言語の実装よりも扱いにくい、または流暢でない場合があります。

とは言っても、ネイティブC++言語機能yield in PythonまたはJavaScriptが行う機能。

別の例は並行プログラミングです。 C++ 0xにはそれについての発言権がありますが、現在の標準にはありません。同時実行性はまったく新しい考え方です。

また、迅速な開発は、シェルプログラミングでさえ、C++プログラミングの領域を離れない限り、決して学ぶことのないことです。

5
wilhelmtell

コルーチン は、C++よりも他の言語の具体的なメリットの多くを支える非常に便利な言語機能です。それらは基本的に追加のスタックを提供するので、関数を中断して継続することができ、パイプラインのような機能を言語に提供して、フィルターを介して操作の結果を他の操作に簡単に送ることができます。それは素晴らしいです、そしてRubyで私はそれが非常に直感的でエレガントであることがわかりました。遅延評価はこれにも結びついています。

イントロスペクションとランタイムコードのコンパイル/実行/評価/何でも、C++にはない強力な機能です。

5
Tony

LISPとC++の両方にコンピューター代数システムを実装したので、私はその言語の完全な初心者であったとしても、LISPでの作業ははるかに簡単だったと言えます。リストにあるすべてのもののこの単純な性質は、非常に多くのアルゴリズムを単純化します。確かに、C++バージョンは何十億倍も高速でした。ええ、私はLISPのバージョンをより速くすることはできましたが、コードはLispyにはなりません。たとえば、スクリプトが常に簡単になるもう1つのことは、LISPです。仕事に適したツールを使用することがすべてです。

ある言語が別の言語よりも「強力」であると私たちが言うとき、どういう意味ですか?言語が「表現力豊か」と言うときまたは「金持ち?」 I think問題の説明が簡単かつ自然になるほど視野が狭まると、言語が力を発揮することを意味します-本当に状態遷移ですよね? -それはその見方の中に住んでいます。それでも、その言語は、私たちの視野が広がると、それほど強力ではなくなり、表現力が低下し、役に立たなくなります。

言語が「強力」で「表現力」があるほど、その使用は制限されます。したがって、「パワフル」と「表現力」は、実用性の低いツールに使用するのに間違った言葉かもしれません。おそらく、「適切」または「抽象的」という言葉がそのようなものに適しています。

私はプログラミングの低レベルのものをたくさん書くことから始めました。割り込みルーチンを備えたデバイスドライバー。埋め込みプログラム;オペレーティングシステムコード。コードはハードウェアと親密で、すべてアセンブリ言語で記述しました。アセンブラが最も抽象的なものであるとは言いませんが、それでも、アセンブラはそれらの中で最も強力で表現力のある言語です。アセンブリ言語で問題を表現できます。とても強力なので、どんなマシンでも好きなようにできます。

そして、後で高水準言語を理解したことはすべて、アセンブラーでの私の経験にすべてを負っています。結局のところ、どんなに抽象的であっても、すべてがハードウェアに対応しなければならないため、後で学んだことはすべて簡単でした。

ますます高いレベルの抽象化、つまり視野が狭くなることを忘れたいと思うかもしれません。いつでも後で受け取ることができます。ほんの数日で簡単に習得できます。私の意見では、ハードウェアの言語を学ぶ方がよいでしょう1、できるだけ骨に近づけます。


1 おそらくあまり密接な関係はありませんが、carcdrはハードウェアから名前を取得します。最初のLISPは、実際のデクリメントレジスタと実際のアドレスレジスタを備えたマシンで実行されました。どうだい?

2
Pete Wilson

連想配列

データを処理する一般的な方法は次のとおりです。

  • 入力を読み取り、そこから階層構造を構築し、
  • その構造のインデックスを作成する(たとえば、異なる順序)
  • それらの抽出物(フィルタリングされた部分)を作成
  • 値または値のグループ(ノード)を見つける、
  • 構造の再配置(ノードの削除、ルールに基づくサブエレメントの追加、追加、削除など)、
  • ツリーをスキャンして、それらの一部を印刷または保存します。

そのための適切なツールは連想配列です。

  • 私が見た連想配列の最良の言語サポートは[〜#〜] mumps [〜#〜]です。ここで、連想配列は次のとおりです。1.常にソートされる2.ディスク上に作成できる(したがって-データベースと呼ばれます)、構文はほとんど同じです。 (副作用:データベースとして非常に強力です。プログラマーはネイティブのbtreeにアクセスできます。史上最高のNoSQLシステムです。)
  • 私の2番目の賞品は[〜#〜] php [〜#〜]ですforeach$ a [ x]のような簡単な構文が好きです==または$ a [x] [y] [z] ++

a [x] [y] [z 8] ==のように作成できないため、JavaScriptの連想配列構文はあまり好きではありません。最初に作成する必要があるのはa [ x]およびa [x] [y]

さて、C++(およびJava)にはMapMultimapなどのコンテナクラスの素晴らしいポートフォリオがありますが、スキャンしたい場合はイテレータを作成する必要があります、そして新しい深いレベルの要素を挿入したい場合、すべての上位レベルなどを作成する必要があります。不快です。

C++(およびJava)には使用可能な連想配列がないとは言いませんが、型なし(または厳密に型指定されていない)スクリプト言語は、型なしのスクリプト言語であるため、コンパイルされたものよりも優れています。

免責事項:C#や他の.NET言語に精通していません。よく連想配列を処理しています。

2
ern0

私はJava、C\C++、アセンブリ、およびJavaスクリプトを学びません。C++を使用して生計を立てています。

ただし、アセンブリプログラミングとCプログラミングの方が好きだと言わざるを得ません。これは主に命令型プログラミングとインラインです。

プログラミングパラダイムは、データ型を分類するために重要であり、強力な設計パターンとコードの形式化を可能にする高度なプログラミング抽象化概念を提供することを知っています。ある意味では、各パラダイムは、基礎となるハードウェアレイヤーを抽象化するためのパターンとコレクションのコレクションであるため、マシン内部のEAXまたはIPについて考える必要はありません。

これに関する私の唯一の問題は、機械がどのように機能するかについての人々の概念と概念を、イデオロギーの何が起こっているのかの曖昧な主張に変えることを可能にすることです。このパンは、プログラマーのいくつかのイデオロギー目標への要約に加えて、あらゆる種類の素晴らしい抽象化です。

結局のところ、CPUとは何か、コンピュータが内部でどのように機能するのかについて、明確な考え方と境界を持つことが望ましいです。 CPUが気にしているのは、メモリのデータをメモリからレジスタに移動して命令を実行する一連の命令を実行することだけです。データ型の概念、またはそれ以上のプログラミング概念はありません。データを移動するだけです。

私たちの世界観はすべて異なるため、プログラミングパラダイムをミックスに追加すると、より複雑になります。

2
Chad

c ++でのみ記述している場合、概念化または実装が難しい言語で使用する強力な言語機能またはイディオムはありますか?

他の言語で遭遇し​​た、c ++で作成または「考え」ていると概念化するのが難しいと思われる便利な概念や技法はありますか?

C++は多くのアプローチを扱いにくくします。 C++に限定すると、プログラミングのほとんどを概念化するのが難しいとまで言えます。以下は、C++によって困難になる方法ではるかに簡単に解決される問題の例です。

レジスタ割り当てと呼び出し規約

多くの人々はC++をベアメタル低レベル言語と考えていますが、実際にはそうではありません。マシンの重要な詳細を抽象化することにより、C++はレジスター割り当てや呼び出し規約などの実用性を概念化することを困難にします。

このような概念について学ぶには、いくつかのアセンブリ言語プログラミングを試してみることをお勧めします この記事ARMコード生成品質 について。

ランタイムコード生成

C++しか知らない場合は、おそらくテンプレートがメタプログラミングのすべてであり、すべてであると考えるでしょう。そうではありません。実際、それらはメタプログラミングにとって客観的に悪いツールです。別のプログラムを操作するプログラムはすべて、メタプログラムであり、インタープリター、コンパイラー、コンピューター代数システム、定理証明プログラムが含まれます。ランタイムコード生成は、このための便利な機能です。

メタサーキュラー評価について学ぶために、Schemeの実装を起動してEVALを試すことをお勧めします。

木を操作する

ツリーはプログラミングのいたるところにあります。構文解析では、抽象構文ツリーがあります。コンパイラーには、ツリーであるIRがあります。グラフィックとGUIプログラミングでは、シーンツリーがあります。

これは "C++用の途方もなくシンプルなJSONパーサー" は、C++には非常に小さいLOC 484にすぎません。次に、それを 私の独自の単純なJSONパーサー と比較してください。違いは主に、MLの代数的データ型とパターンマッチング(アクティブパターンを含む)により、ツリーの操作が非常に簡単になるためです。

OCamlの赤黒木 もチェックしてください。

純粋に機能的なデータ構造

C++にはGCがないため、いくつかの有用なアプローチを採用することは事実上不可能です。純粋に機能的なデータ構造は、そのようなツールの1つです。

たとえば、OCamlでこの 47行の正規表現マッチャー を確認してください。簡潔さは、主に純粋に機能的なデータ構造の広範な使用によるものです。特に、セットであるキーを持つ辞書の使用。 stdlibディクショナリとセットはすべて変更可能ですが、ディクショナリのキーを変更したり、コレクションを壊したりすることはできないため、C++ではこれは本当に困難です。

論理プログラミングと元に戻すバッファーは、純粋に関数型のデータ構造がC++では難しいことを他の言語で本当に簡単にする他の実用的な例です。

テールコール

C++は末尾呼び出しを保証しないだけでなく、デストラクタが末尾位置での呼び出しの邪魔になるため、RAIIは基本的にそれと矛盾します。末尾呼び出しでは、制限された量のスタックスペースのみを使用して、制限のない数の関数呼び出しを行うことができます。これは、拡張可能なステートマシンを含むステートマシンを実装するのに最適であり、他の多くの厄介な状況では「刑務所から解放される」優れたカードです。

たとえば、金融業界のこの 継続渡しスタイルを使用したF-1のメモ化を伴う0-1ナップザック問題の実装 を確認してください。末尾呼び出しがある場合、継続渡しスタイルは明白な解決策になりますが、C++では扱いにくくなっています。

並行性

もう1つの明白な例は、並行プログラミングです。これはC++では完全に可能ですが、他のツールに比べてエラーが発生しやすく、特にErlang、ScalaおよびF#などの言語で見られるような順次プロセスの通信です。

2
Jon Harrop

これは古い質問ですが、誰も言及していないので、リスト(そして今は口述)内包表記を追加します。 HaskellまたはPythonでFizz-Buzzの問題を解決するワンライナーを書くのは簡単です。C++でそれを試してください。

C++はC++ 11で近代化への大規模な移行を行いましたが、「近代的な」言語と呼ぶのは少しストレッチです。 C++ 17(まだリリースされていません)は、「モダン」が「前の千年のものではない」ことを意味する限り、現代の標準に近づくための動きをさらに進めています。

Python(およびGuidoの79文字の行の長さ制限に従う)で1行で記述できる最も単純な理解でさえ、C++に変換するとコードの行が非常に多くなり、その一部がC++コードの行はかなり複雑です。

1
David Hammen

ユーザー定義クラスのユーザー定義メンバー関数であるコールバックを呼び出すコンパイル済みライブラリ。


これはObjective-Cで可能であり、ユーザーインターフェイスのプログラミングを簡単にします。ボタンを言うことができます:「押したときに、このオブジェクトに対してこのメ​​ソッドを呼び出してください」とボタンはそうします。好きなコールバックのメソッド名を自由に使用できます。ライブラリコードでフリーズされていません。それを機能させるためにアダプターから継承する必要はありません。また、コンパイラーはコンパイル時に呼び出しを解決する必要もありません。同様に重要なのは、同じオブジェクトの2つの異なるメソッドを呼び出すように2つのボタンに指示することです。

他の言語でコールバックを定義するための同様に柔軟な方法はまだ見たことがありません(ただし、それらについて聞いて非常に興味があります!)。 C++で最も近いものは、おそらく、必要な呼び出しを実行するラムダ関数を渡すことです。これにより、ライブラリコードがテンプレートに制限されます。

言語が渡す能力を評価することを私に教えてくれたのはObjective-Cのこの機能ですanyオブジェクトのタイプ/関数/ whatever-important-concept-the-language-contains自由に沿ってそれらを変数に保存する機能を備えています。あらゆるタイプの概念を定義しているが、利用可能なすべての種類の変数にそれを格納する手段を提供していない言語の任意のポイントは、重大な障害であり、おそらく非常に醜い情報源です。重複したコード。残念ながら、バロックプログラミング言語は次のような点を示す傾向があります。

  • C++では、VLAのタイプを書き留めたり、VLAへのポインターを格納したりすることはできません。これにより、動的サイズの真の多次元配列(C99以降Cで使用可能)が事実上禁止されます。

  • C++では、ラムダのタイプを書き留めることはできません。 typedefすることもできません。したがって、ラムダを渡したり、オブジェクトへの参照を格納したりする方法はありません。 Lambda関数はテンプレートにのみ渡すことができます。

  • Fortranでは、名前リストのタイプを書き留めることはできません。名前リストをあらゆる種類のルーチンに渡す手段はありません。したがって、2つの異なる名前リストを処理できる複雑なアルゴリズムがある場合は、運が悪いです。アルゴリズムを一度作成して、それに関連する名前リストを渡すことはできません。

これらはほんの数例ですが、共通のポイントがあります。そのような制限を初めて目にするときはいつでも、禁止されていることをするのはとてもおかしな考えのように思われるので、通常は気にしません。ただし、その言語で本格的なプログラミングを行うと、最終的には、この正確な制限が本当の迷惑になるポイントに到達します。