「推論するのは簡単」-それはどういう意味ですか?[閉まっている]


49

他の開発者がそのフレーズを使用して、いくつかのパターンを「宣伝」したり、ベストプラクティスを開発したりすることを何度も耳にしました。ほとんどの場合、このフレーズは、関数型プログラミングの利点について話しているときに使用されます。

「推論するのは簡単」というフレーズは、説明やコードサンプルなしでそのまま使用されています。だから私にとっては、次の「話題」の言葉のようになり、より「経験のある」開発者が講演で使用します。

質問:「推論するのは簡単ではない」例をいくつか挙げてください。「推論するのは簡単」の例と比較できますか?


4
広く使用され、より正確なフレーズ@MartinMaat等式推論ですが、私はこれはファビオの後には何かあるかもしれないことをお勧めしたい
JK。

3
この種のことには「認知的負荷」というフレーズを使用するのが好きです。
Baldrickk

16
プログラムについての推論が何を意味するか知っていますか?
ベルギ

5
非形式的な意味で、私はこれを使用して、ソリューションがテストを行わずに与えられた入力に対して結果がどうなるかを(一般的に)十分に理解することを意味します。これは、あらゆる入力セットに対して、結果が驚くことではないことを意味します。たとえば、明白でないコーナーケースがあるソリューションは、推論するのが困難です。主に、堅牢性に関してこれを使用します。
ジミージェームズ

7
私は「推論しやすい」を頻繁に使用することに対して非常に有罪です。ただし、絶対的簡単さではなく、比較的簡単なことを言うように注意していることに注意してください。私の人生には、ソフトウェアがまったくないという理由がある日があったので、その日は簡単ではありませんでした。多大な時間と労力を費やすだけで簡単になりました。プログラミングの問題は簡単だと言うことは、それを(まだ)簡単に見つけられない人に対して軽pe的な態度を取ることです。あるモデルが別のモデルより簡単だということは、関係する概念が少なく、可動部品が少ないなどということです。
エリックリッパー

回答:


58

私の考えでは、「推論しやすい」というフレーズは、「頭の中で実行する」のが簡単なコードを指します。

コードの一部を見て、短く、明確に記述され、適切な名前と値の最小限の変異がある場合、コードが何をするかを精神的に作業することは(比較的)簡単な作業です。

貧弱な名前、値を絶えず変化させる変数、複雑な分岐を伴う長いコードには、通常、現在の状態を追跡するのに役立つペンと紙が必要です。そのため、このようなコードは頭の中で簡単に処理することはできません。そのため、このようなコードを推論するのは簡単ではありません。


29
変数にどれだけ名前を付けても、Goldbachの推測を反証しようとするプログラムは、頭の中で、または他の場所で「実行」することが本質的に困難です。しかし、反例を見つけたと主張するなら、それは真実を告げていると自分自身を説得するのが容易であるという意味で、それについて推論するのはまだ簡単です;
スティーブジェソップ

4
私は頭の中でコードを実行したくありません。それは、私にとって、「推論するのは簡単ではない」という究極のショーになるでしょう。コンピューター実行せずに何をするかについての予測ステートメントを作成できるようにしたいと思います。「推論しやすい」コードは、頭の中で実行する必要のないコードであり、代わりに推論することができます。
コートアンモン

1
正式な検証に言及することなく、コードに関する推論に関する質問にどのように答えることができますか?この回答は、コードに関する推論が非公式でアドホックであることを示唆しています。そうではなく、通常非常に細心の注意と数学的アプローチで行われます。客観的な意味でコードを「推論しやすくする」特定の数学的プロパティがあります(純粋な関数、非常に簡単な例を示します)。変数の名前は、少なくとも正式な意味ではコードの「理由」がどれほど簡単かには関係ありません
ポリグノーム

3
@Polygnomeコードに関する推論は通常、細心の注意と数学的アプローチでは行われません。私がこれを書いているとき、コードについて非公式に推論している人々は、少なくとも数百万人、数え切れないほど数学的アプローチの数を上回っています。
カズ

2
@Polygnome- "Code easy to reason about" almost exclusively alludes to its mathematical properties and formal verificationそれはおおよそ質問への答えのように聞こえます。コメントに(主観的な)回答が何であるかについて意見を異にするのではなく、回答として投稿することもできます。
ダケリング

47

メカニズムまたはコードの一部は、それが何をするかを予測するためにいくつかのことを考慮する必要がある場合に簡単に推論でき、考慮する必要があることは簡単に利用できます。

副作用や状態のない真の機能は、出力が入力によって完全に決定されるため、簡単に推論することができます。入力はパラメーター内にあります。

逆に、状態を持つオブジェクトは、メソッドが呼び出されたときにオブジェクトの状態を考慮する必要があるため、推論するのがはるかに困難です。これは、オブジェクトが特定の状態。

さらに悪いのは、グローバル変数です。グローバル変数を読み取るコードについて考えるには、コード内で変数を設定できる場所とその理由を理解する必要があります。

理由について考えるのが最も難しいのは、共有状態のマルチスレッドプログラミングです。状態があるだけでなく、複数のスレッドが同時にそれを変更しているため、1つのスレッドで実行されたときにコードが何をするかを推論するために、実行のすべての単一ポイントで、他のスレッド(またはそれらのいくつか)がコードの他のほとんどの部分を実行し、操作中のデータを目の下で変更する可能性を考慮しなければなりません。理論的には、mutex / monitors / criticalセクション/ what-you-call-itで管理できますが、実際には、共有状態および/または並列処理を非常に小さく制限しない限り、実際に単なる人間がそれを確実に行うことはできませんコードのセクション。


9
私はこの答えに同意しますが、純粋な関数であっても、宣言的なアプローチ(CSS、XSLT、またはmakeC ++テンプレートの特殊化と関数のオーバーロードなど)で、プログラム全体を考慮する立場に戻すことができます。何かの定義を見つけたと思ったとしても、言語ではプログラム内のどこでもより具体的な宣言をオーバーライドできます。IDEがこれに役立つ場合があります。
スティーブジェソップ

4
また、マルチスレッドシナリオでは、コードがどの下位レベルの命令に脱糖するかについても十分に深く理解する必要があることを付け加えます。
ジャレッド・スミス

6
@SteveJessop:実際、この点はしばしば見落とされています。C#は、あなたが作る理由があると言うあなたは方法は静かにデフォルトをoverridability作るのではなく、オーバーライドになりたいときは、この時点で、「プログラムの正確性は、コンパイル時に見つけられないコードに依存する可能性がある」というフラグを振ることを希望します。(そうは言っても、C#のクラスのデフォルトは「封印された」ことです。)
エリックリッパー

@EricLippert sealedデフォルトではない最終的な理由は何ですか?
ZEVスピッツ

@ZevSpitz:その決定は私の時間よりもずっと前になされました。知りません。
エリックリッパー

9

関数型プログラミングの場合、「推論するのが簡単」の意味は主に決定論的であるということです。それにより、特定の入力が常に同じ出力につながることを意味しました。そのコードに触れない限り、プログラムに対して何でもできますが、壊れることはありません。

一方、オブジェクト指向オブジェクトは、生成される「出力」が関連するすべてのオブジェクトの内部状態に依存するため、通常、推論するのがより困難です。それが現れる典型的な方法は、予期しない副作用です。コードの一部を変更すると、一見無関係な部分が壊れます。

...関数型プログラミングのマイナス面はもちろん、実際には、やりたいことの多くはIOと状態の管理です。

しかし、推論するのがより難しい他の多くの事柄があり、同時性が主な例であることに私は@Kilianに同意します。分散システムも。


5

幅広い議論を避け、特定の質問に対処する:

「推論するのは簡単ではない」の例をいくつか挙げてください。「推論するのは簡単」の例と比較できますか?

私は、「本物のプログラマー、メルの物語」を参照します。これは、1983年にまで遡り、したがって「伝説」と見なされるプログラマーの民間伝承です。

それは、自己参照および自己修正コード、およびマシンのバグの意図的な悪用を含む、可能な限り不可解なテクニックを好むコードを書くプログラマーの物語を伝えます:

実際、明らかな無限ループは、桁上げエラーを利用するようにコーディングされていました。「アドレスxからロード」としてデコードされた命令に1を追加すると、通常「アドレスx + 1からロード」が生成されました。しかし、xがすでに可能な限り最高のアドレスであった場合、アドレスはゼロにラップアラウンドしただけでなく、オペコードが読み取られるビットに1が持ち込まれ、オペコードが「load from」から「jump to」に変更されました。完全な命令が「最後のアドレスからロード」から「アドレス0にジャンプ」に変更されたこと。

これは「推論するのが難しい」コードの例です。

もちろん、メルは同意しません...


1
私の長年のお気に入りの1つであるメルの物語を参照するための+1。
ジョンボリンジャー

3
ウィキペディアの記事はリンクしていないので、ここでメルの物語を読んでください。
TRiG

ページの@TRiG脚注3ですか?
AakashM

@AakashMなんとかそれを逃した。
TRiG

5

例と非常に一般的な例を提供できます。

次のC#コードを検討してください。

// items is List<Item>
var names = new List<string>();
for (var i = 0; i < items.Count; i++)
{
    var item = items[i];
    var mangled = MyMangleFunction(item.Name);
    if (mangled.StartsWith("foo"))
    {
        names.Add(mangled);
    }
}

次に、この代替案を検討してください。

// items is List<Item>
var names = items
    .Select(item => MyMangleFunction(item.Name))
    .Where(s => s.StartsWith("foo"))
    .ToList();

2番目の例では、このコードが何をしているのかが一目でわかります。を見るとSelect、アイテムのリストが他のリストに変換されていることがわかります。を見るとWhere、特定のアイテムが除外されていることがわかります。一目で、私はそれが何であるかを理解しnames、それを有効に活用できます。

forループを見ると、実際にコードを読み終えるまで、ループで何が起こっているのかわかりません。そして時々、私はすべての副作用を説明したことを確認するためにそれをたどる必要があります。名前が(型定義を超えて)何であり、どのようにそれを効果的に使用するかを理解するようになるには、少し作業が必要です。したがって、最初の例は2番目の例よりも推論するのが困難です。

最終的に、ここについて簡単に推論できるかどうかは、LINQメソッドSelectとを理解することにかかっていますWhere。それらがわからない場合、2番目のコードを最初に推論するのは困難です。しかし、あなたはそれらを一度理解するための費用を支払うだけです。forループを変更するたびに使用するたびに、ループを理解するためのコストを支払います。場合によっては、費用を支払う価値がありますが、通常は「推論しやすい」ことがはるかに重要です。


2

関連するフレーズは(I言い換え)、

コードに「明らかなバグがない」だけでは不十分です。代わりに、「明らかにバグがない」必要があります。

比較的「推論しやすい」例はRAIIです。

別の例としては、致命的な抱擁の回避が考えられます。ロックを保持して別のロックを取得でき、ロックが多数ある場合、致命的な抱擁が発生する可能性のあるシナリオがないことを確認するのは困難です。「(グローバル)ロックは1つしかない」、「最初のロックを保持している間は2番目のロックを取得することはできません」などのルールを追加すると、システムの推論が比較的容易になります。


1
うーん。RAIIが簡単に推論できるかどうかはわかりません。もちろん、概念的に理解するのは簡単ですが、RAIIを広範囲に使用するコードの動作を実際に推論(予測)することはさらに難しくなります。つまり、基本的にはスコープレベルでの目に見えない関数呼び出しです。多くの人々がこれについて推論するのに苦労しているという事実は、あなたがCOMプログラミングをしたことがあるなら、非常に明白です。
コーディグレー

私は比較的簡単であることを意味しました(Cと比較してC ++):たとえば、言語サポートコンストラクターの存在は、プログラマーが初期化を忘れたオブジェクトを作成/使用/使用できないことを意味します。
ChrisW

このCOMベースの例では、スタイル、つまりC ++スタイルのスマートポインター(CComPtr<>)とCスタイルの関数(CoUninitialize())が混在しているため、問題があります。Iで例えば、あまりにも、これまでのところ、私はモジュールスコープでお呼び出しのCoInitialize / CoUninitializeの覚えていると、モジュール全体の寿命のために、それを奇妙な例を見つけるmainかでDllMain例に示すように、そしてないいくつかの小さな短命ローカル関数スコープに。
ChrisW

これは、説明のために非常に単純化した例です。COMはモジュールスコープで初期化されることは完全に正しいですが、レイモンドの例(Larryの例のようなmain)がアプリケーションのエントリポイント()関数であると想像してください。起動時にCOMを初期化し、終了する直前に初期化を解除します。RAIIパラダイムを使用するCOMスマートポインターなどのグローバルオブジェクトがある場合を除きます。ミキシングスタイルについて:ctorでCOMを初期化し、dtorで初期化されていないグローバルオブジェクトは実行可能であり、Raymondが示唆するものですが、微妙であり、推論するのは容易ではありません。
コーディグレー

私は、すべてが明示的な関数呼び出しであるため、多くの点で、CでCOMプログラミングを推論する方が簡単だと主張します。背中の後ろに隠れているものも見えないものもありません。手動ですべての関数呼び出しを記述し、戻って作業を確認し、正しく実行されたことを確認する必要があるため、もう少し作業が多くなります(つまり、退屈です)。簡単に推論できるようにします。言い換えると、「スマートポインターがスマートすぎる場合がある」ということです。
コーディグレー

2

プログラミングの要点は、ケース分析です。アラン・ペルリスはエピグラム#32でこれについて次のように述べています。プログラマーは、その独創性と論理によってではなく、ケース分析の完全性によって評価されるべきです。

ケース分析が簡単かどうかについて、状況を簡単に判断できます。これは、考慮する必要のあるケースがほとんどないこと、またはそれを満たさない場合、特別なケースがほとんどないことを意味します。

たとえば、アルゴリズムの再帰バージョンは通常、命令型バージョンよりも推論が容易です。これは、再帰バージョンに現れない状態変数をサポートする突然変異によって生じる余分なケースに寄与しないためです。さらに、再帰の構造は、数学的帰納的証明パターンに適合するようなものです。ループバリアントや最も弱い厳密な前提条件などの複雑さを考慮する必要はありません。

これの別の側面は、ケーススペースの構造です。階層的なケースの状況と比較して、フラットな、またはほとんどフラットなケースへの分割がある状況(サブケースがあるケースとサブサブケースなど)について推論するのは簡単です。

推論を単純化するシステムの特性は直交性です。これは、サブシステムを管理するケースが、それらのサブシステムが結合されたときに独立したままになる特性です。組み合わせによって「特別なケース」が生じることはありません。4ケースの場合と3ケースの場合とが直交して組み合わされる場合、12のケースがありますが、理想的には各ケースは、独立したままの2つのケースの組み合わせです。ある意味では、実際には12のケースはありません。これらの組み合わせは、「発生するケースのような現象」であり、心配する必要はありません。これが意味することは、他のサブシステムの他の3つを考慮せずに考えることができる4つのケースがまだあるということです。いくつかの組み合わせを特別に識別し、追加のロジックを付与する必要がある場合、推論はより困難になります。最悪の場合、すべての組み合わせに特別な処理があり、実際には12の新しいケースがあり、これらは元の4と3に追加されます。


0

承知しました。並行性を取る:

ミューテックスによって適用されるクリティカルセクション:原則は1つ(2つの実行スレッドが同時にクリティカルセクションに入ることはできない)であるため理解しやすいが、非効率とデッドロックの両方が発生しやすい。

ロックフリープログラミングやアクターなどの代替モデル:「エレガントにパワフルになる可能性がありますが、理解するのは非常に困難です」

簡単に推論することは、メソッドの1つの側面です。ただし、使用する方法を選択するには、すべての側面を組み合わせて考慮する必要があります。


13
-1:本当に、本当に悪い例です。このフレーズが自分自身の意味を理解していないと思います。「ミューテックスによって強制されるクリティカルセクション」は、実際のところ、そこから推論するのが最も難しいものの1つです。ロックフリープログラミングを提供しますが、アクターモデルの全体的なポイントは、それがはるかに簡単に推論できるということです。
マイケルボルグワード

1
問題は、並行性自体がプログラマーにとって推論するのが非常に難しいトピックであるため、非常に良い例にならないことです。ロックフリープログラミングと比較して、ミューテックスによって実施されるクリティカルセクションは並行性を実装する比較的簡単な方法であることは完全に正しいですが、ほとんどのプログラマーはマイケルのようであり、クリティカルセクションとミューテックスについて話し始めると目が凝ります。確かに理解するのは簡単なことのように思えません。すべてのバグは言うまでもありません。
コーディグレー

0

タスクを正式な推論に限定しましょう。ユーモラスな、発明的、または詩的な推論には異なる法則があるためです。

それでも、式は暗く定義されており、厳密な方法で設定することはできません。しかし、それは私たちにとってそれほど薄暗いままであるべきという意味ではありません。構造がいくつかのテストに合格し、異なるポイントのマークを取得していると想像してみましょう。すべてのポイントの良い点は、構造があらゆる面で便利であり、したがって「推論するのが簡単」であることを意味します。

「推論するのが簡単」な構造は、次の点で良い評価を得ます。

  • 内部用語には、合理的で簡単に識別および定義された名前があります。要素に何らかの階層がある場合、親と子の名前の違いは、兄弟の名前の違いとは異なるはずです。
  • 構造要素の種類の数が少ない
  • 構造要素の使用タイプは、私たちが慣れている簡単なものです。
  • 理解しにくい要素(再帰、メタステップ、4 +次元ジオメトリなど)は分離されており、互いに直接結合されていません。(たとえば、1,2,3,4..n..dimensionalキューブの再帰規則を変更しようとすると、非常に複雑になります。しかし、これらの各規則を数式に変換する場合は、 nに応じて、nキューブごとに式を個別に作成し、そのような式の再帰規則を個別に作成します。2つの構造は個別に簡単に考えることができます)
  • 構造要素のタイプは明らかに異なります(たとえば、0と1から始まる混合配列を使用しない)

テストは主観的ですか?はい、当然です。しかし、表現自体も主観的です。ある人にとっては簡単ですが、別の人にとっては簡単ではありません。したがって、テストはドメインごとに異なる必要があります。


0

関数型言語が推論できるという考えは、その歴史、具体的には、計算可能関数ロジックが推論に使用した構成体に類似したプログラミング言語として開発されたMLから来ています。ほとんどの関数型言語は、命令型言語よりも正式なプログラミング計算に近いため、コードから推論システムの入力への翻訳はそれほど面倒ではありません。

推論システムの例として、パイ計算では、命令型言語の各可変メモリロケーションを個別の並列プロセスとして表す必要がありますが、機能操作のシーケンスは単一のプロセスです。LFC定理証明から40年、私たちはGBのRAMで作業しているので、数百のプロセスを持っていることは問題ではありません-数百行のC ++から数百行のC ++推論システムが約3GBの状態空間を使い果たし、断続的なバグを解消したプロセス。これは70年代には不可能でしたが、1990年代初頭にはスーパーコンピューターが必要でしたが、同様のサイズの関数型言語プログラムの状態空間は当時のことを考えるには十分に小さかったのです。

他の答えから、この句は、命令型言語について推論するのを困難にした困難の多くがムーアの法則によって侵食されているかのように、話題句になりつつあります。


-2

簡単に推論できるのは文化的に特定の用語であるため、具体的な例を見つけるのは非常に困難です。それは推論を行うことになっている人々に固定されている用語です。

「推論するのは簡単」は実際には非常に自己記述的なフレーズです。コードを見ていて、それが何をするのかを推論したい場合は、簡単です=)

さて、それを分解します。コードを見ている場合、通常は何かをしたいでしょう。あなたはそれがあなたがすべきだと思うことをすることを確認したい。そのため、コードが何をすべきかについての理論を開発し、それについて推論して、コードが実際に機能する理由を主張しようとします。コードを人間のように(コンピューターではなく)考え、コードができることについての議論を合理化しようとします。

「推論しやすい」の最悪のケースは、コードが何をするのかを理解する唯一の方法が、すべての入力に対してチューリングマシンのようにコードを1行ずつ実行することである場合です。この場合、コードについて何かを推論する唯一の方法は、自分をコンピューターに変えて頭の中で実行することです。これらの最悪の例は、RSAを復号化する次の3行のPERLなど、難読化されたプログラミングコンテストで簡単に見られます。

#!/bin/perl -sp0777i<X+d*lMLa^*lN%0]dsXx++lMlN/dsM0<j]dsj
$/=unpack('H*',$_);$_=`echo 16dio\U$k"SK$/SM$n\EsN0p[lN*1
lK[d2%Sa2/d0$^Ixp"|dc`;s/\W//g;$_=pack('H*',/((..)*)$/)

簡単に言うと、この用語は非常に文化的です。あなたが考慮する必要があります:

  • 推論システムにはどんなスキルがありますか?どのくらいの経験ですか?
  • 推論者はコードについてどのような質問をするでしょうか?
  • 推論システムはどの程度確実である必要がありますか?

これらはそれぞれ、「推論するのが簡単」に異なる影響を及ぼします。推論者のスキルを例に取ります。私が会社を始めたとき、「推論するのが簡単」であるため、MATLABでスクリプトを開発することをお勧めしました。どうして?さて、会社の全員がMATLABを知っていました。別の言語を選択した場合、誰もが私を理解するのが難しくなります。MATLABの読みやすさは、単にタスク用に設計されたものではないという理由だけで、一部のタスクにとってひどいものであることを決して忘れないでください。その後、私のキャリアが進むにつれて、Pythonはますます人気を博しました。突然MATLABコードは「推論するのが難しい」ようになり、Pythonは推論するのが簡単なコードを書くための好みの言語でした。

また、読者が持っているかもしれないidomを検討してください。特定の構文でFFTを認識するために読者に頼ることができる場合、その構文に固執する場合、コードについて「推論するのがより簡単です」。これにより、テキストファイルを、FFTをペイントしたキャンバスとして見ることができます。C ++を使用している場合は、読者がstdライブラリにどの程度慣れているかを調べてください。関数型プログラミングはどれくらい好きですか?コンテナライブラリから出てくるイディオムのいくつかは、どのイデオムスタイルを好むかに非常に依存しています。

また、読者が回答に関心を寄せる可能性がある質問の種類を理解することも重要です。読者は主にコードの表面的な理解に関心がありますか、それとも腸の奥深くにバグを探していますか?

読者がいかに確実でなければならないかは、実際には興味深いものです。多くの場合、あいまいな推論は実際に製品をドアから出すのに十分です。FAAフライトソフトウェアなどの他の場合、読者は確固たる推論を得たいと思うでしょう。特定のタスクにRAIIを使用することを主張するケースに遭遇しました。「セットアップするだけでそれを忘れることができます...正しいことをするからです」。私はそれについて間違っていると言われました。このコードについて推論しようとしていた人たちは、「詳細を忘れたいだけ」という人たちではありませんでした。彼らにとって、RAIIは吊り下げ式のチャドに似ていて、あなたがスコープを離れるときに起こりうるすべてのことを考えさせる。


12
Perlコードは読みにくいです。理由はありません。理解する必要がある場合は、コードの難読化を解除します。実際に推論するのが難しいコードは、すべての明確な識別子でうまくフォーマットされており、コードゴルフのトリックがないとき、それはまだ推論するのが難しいものです。
カズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.