関数型、宣言型、命令型プログラミング[終了]


466

関数型、宣言型、命令型プログラミングという用語はどういう意味ですか?


3
ここにいくつかの素晴らしい答えがあります。完全に解明されていない興味深いことの1つは、宣言命令型は、単なる異なるスタイルや、どのようにではなく、補完的で共生的であることです。
キット

1
@Kit Imo、このページの回答の一部は用語を混同しています。DP ==参照透過性(RT)。DPとIPは正反対です。したがって、全体を補完するものではありません。つまり、プログラム全体をどちらのスタイルでも記述できます。関数の呼び出しはDP(RT)またはIPのいずれかであり、その実装はどちらか、またはそれらの組み合わせです。他のDP関数でIP関数を呼び出すと、DP関数のIPを呼び出すことができるという意味で、それらは共生しません。それらは、実世界の(例:機能的反応)プログラムが混合を使用できるという意味で共生しています。
シェルビームーアIII

wikiに追加するか、wikiに類似したリンクなどに追加する必要があります。ここにwikipediaの素晴らしいリンクがありますen.wikipedia.org/wiki/Comparison_of_programming_paradigms
Joe


1
この質問はMetaで議論されています:meta.stackoverflow.com/q/342784/2751851
duplode

回答:


262

これを書いている時点では、このページで投票された上位の回答は不正確であり、ウィキペディアを引用する回答を含め、宣言的定義と命令的定義が混同されています。いくつかの回答は、さまざまな方法で用語を融合させています。

数式がセルを変更するかどうかに関係なく、スプレッドシートプログラミングが宣言型である理由についての私の説明も参照してください。

また、関数型プログラミングは宣言型のサブセットでなければならないという回答もいくつかあります。その点では、「機能」と「手順」を区別するかどうかにかかっています。最初に命令型と宣言型を比較してみましょう。

宣言式の定義

唯一の可能性を区別することができる属性の宣言型からの発現が不可欠式は、そのサブ式の参照透過性(RT)です。他のすべての属性は、両方のタイプの式の間で共有されるか、RTから派生します。

100%宣言型言語(つまり、可能なすべての式がRTである言語)は、(他のRT要件の中で)保存された値の変更を許可しません(例:HTMLおよびほとんどのHaskell)。

RT式の定義

RTは「副作用なし」と呼ばれることがよくあります。効果という用語には正確な定義がないため、「副作用なし」がRTと同じであることに同意しない人もいます。RTには正確な定義があります。

すべてのサブ式は概念的には関数呼び出しであるため、RTでは、関数の実装(つまり、呼び出された関数内の式)が関数の外部にある可変状態にアクセスできないようにする必要があります(可変ローカル状態へのアクセスは許可されています)。簡単に言えば、関数(実装)は純粋でなければなりません。

純関数の定義

純粋な関数は、「副作用がない」とよく言われます。効果という用語には正確な定義がないため、同意しない人もいます。

純粋関数には次の属性があります。

  • 唯一の観察可能な出力は戻り値です。
  • 唯一の出力依存関係は引数です。
  • 引数は、出力が生成される前に完全に決定されます。

RTは式(関数呼び出しを含む)に適用され、純粋性は関数(の実装)に適用されることに注意してください。

RT式を作成する不純な関数のあいまいな例は並行性ですが、これは純粋さが割り込み抽象化層で壊れているためです。あなたは本当にこれを知る必要はありません。RT式を作成するには、純粋な関数を呼び出します。

RTの派生属性

宣言型プログラミングで引用されたその他の属性、たとえばWikipediaで使用されている1999年引用は、RTに由来するか、命令型プログラミングと共有されています。したがって、私の正確な定義が正しいことを証明します。

外部値の不変性は、RTの要件のサブセットであることに注意してください。

  • 宣言型言語には、ループ制御構造がありません。たとえばfor、およびwhile不変性により、ループ条件が変更されることはありません。

  • 宣言型言語は、入れ子関数の順序(別名論理依存関係)以外の制御フローを表現しません。これは、 不変性により、他の評価順序を選択しても結果が変化しないためです(以下を参照)。

  • 宣言型言語は論理的な「ステップ」(つまり、ネストされたRT関数呼び出し順序)を表現しますが、各関数呼び出しがより高いレベルのセマンティック(つまり「何をするか」)であるかどうかは、宣言型プログラミングの要件ではありません。命令型との違いは、不変性(より一般的にはRT)のため、これらの「ステップ」は変更可能な状態に依存できず、表現されたロジックの関係順序(つまり、関数呼び出しのネストの順序、別名サブ式)にのみ依存しないことです)。

    たとえば、HTML段落<p>は、段落内のサブ式(つまり、タグ)が評価されるまで表示できません。変更可能な状態はありません。タグ階層の論理関係(サブ式のネスト、これは同様にネストされた関数呼び出しです)による順序依存のみです。

  • したがって、不変性(より一般的にはRT)の派生属性があり、宣言式は構成部分(つまり、部分式関数引数)の論理関係のみを表し、変更可能な状態関係は表しません。

評価順序

部分式の評価順序の選択は、関数呼び出しのいずれかがRTではない(つまり、関数が純粋でない)場合にのみ変化する結果をもたらすことができます。たとえば、関数の外部の変更可能な状態が関数内でアクセスされます。

例えば、いくつかのネストされた式を与え、例えばf( g(a, b), h(c, d) )、関数の引数の熱心と遅延評価は機能している場合、同じ結果が得られますfgh純粋です。

一方で、機能ならばfgh純粋でない場合、評価順序の選択は、異なる結果を与えることができます。

ネストされた式は概念的にはネストされた関数です。これは、式演算子が単項接頭辞、単項後置表記、または2進中置表記法に見せかけた単なる関数呼び出しだからです。

接線方向に、すべての識別子、例えばもしabcd、ある不変の機能は常に純粋で、その後、プログラムの外部の状態にアクセスすることができない、どこにでも(つまり、I / O)、および一切アブストラクションレイヤ切れがありません。

ちなみに、Haskellには異なる構文がありf (g a b) (h c d)ます。

評価注文詳細

関数は、入力から出力への状態の遷移(変更可能な格納値ではない)です。純粋関数への呼び出しのRT構成の場合、これらの状態遷移の実行順序は独立しています。副作用がなく、RT関数がキャッシュされた値で置き換えられるという原則があるため、各関数呼び出しの状態遷移は他の関数呼び出しから独立しています人気のある誤解修正するために、純粋なモナドの構成は常に宣言的であり、RTですが、HaskellのIOモナドは間違いなく不純であり、したがってWorldプログラムの外部の状態に対して強制的であるという事実にもかかわらず(ただし、以下の警告の意味では、副作用)分離されています)。

熱心な評価は、関数が呼び出される前に関数の引数が評価されることを意味し、遅延評価は、引数が関数内でアクセスれるまで(およびその場合)引数が評価されないことを意味します

定義:関数パラメーターは関数定義サイトで宣言され、関数引数は関数呼び出しサイトで提供されます。パラメータ引数の違いを知る。

概念的には、すべての式は、例えば、定数は入力せずに関数である関数呼び出し(の組成)は、単項演算子は、一方の入力を有する関数である、バイナリ挿入演算子は、2つの入力を有する関数であり、コンストラクタは関数であり、さらには制御文(例えばifforwhile)関数でモデル化できます。これらのことを順番 引数の関数は(ネストされた関数呼び出しの順序ではない混同ん)が評価されているが、構文で宣言されていない、などがf( g() )熱心に評価することができg、その後fgの結果やそれが評価することができf、唯一いい加減に評価しg、その結果を内部に必要になったときf

注意、チューリング完全な言語(つまり、無限の再帰を可能にする)は完全に宣言的ではありません。たとえば、遅延評価はメモリと時間の不確定性をもたらします。ただし、評価順序の選択によるこれらの副作用は、メモリ消費、実行時間、レイテンシ、非終了、および外部ヒステリシスに限定されているため、外部同期です。

関数型プログラミング

宣言型プログラミングはループを持つことができないため、反復する唯一の方法は関数の再帰です。この意味で、関数型プログラミングは宣言型プログラミングに関連しています。

ただし、関数型プログラミングは宣言型プログラミングに限定されません。機能構成は、特に式の問題に関して、サブタイプの追加と機能分解のいずれかによって拡張を実現できるサブタイピング対照的です。拡張機能は、両方の方法を組み合わせることができます

関数型プログラミングは通常、関数をファーストクラスのオブジェクトにします。つまり、関数の型は、他の型のどこにでも文法に現れることができます。要するに、関数は関数を入力して操作できるため、関数の構成を強調することにより、つまり決定論的計算のサブ計算間の依存関係を分離することで、関心の分離を実現できます。

たとえば、コレクションの各要素に適用される可能性のある無限の可能性のある特殊なアクションのそれぞれに対して、個別の関数を記述する(および関数の宣言が必要な場合はループの代わりに再帰を使用する)代わりに、関数型プログラミングは再利用可能な反復を使用します機能、例えばmapfoldfilter。これらの反復関数は、ファーストクラスの特殊なアクション関数を入力します。これらの反復関数はコレクションを反復処理し、各要素の入力専用アクション関数を呼び出します。これらのアクション関数は、コレクションを反復するためのループステートメントを含める必要がないため、より簡潔です。

ただし、関数が純粋でない場合、それは実際にはプロシージャであることに注意してください。不純な関数を使用する関数型プログラミングは、実際には手続き型プログラミングであると言えるでしょう。したがって、宣言式がRTであることに同意した場合、手続き型プログラミングは宣言型プログラミングではないと言えるため、関数型プログラミングは常にRTであり、宣言型プログラミングのサブセットである必要があると主張できます。

並列処理

ファーストクラスの機能を備えたこの機能構成は、独立した機能を分離することにより、並列処理深さを表現できます。

ブレントの原理:仕事wと深さdの計算は、時間O(max(w / p、d))でpプロセッサPRAMに実装できます。

並行性と並列性の両方とも、宣言型プログラミング、つまり不変性とRT も必要です

では、並列性==同時実行性というこの危険な仮定はどこから来たのでしょうか。それは副作用を伴う言語の自然な帰結です:言語がいたるところに副作用を持っている場合、一度に複数のことを実行しようとするときはいつでも、各操作からの影響のインターリーブによって引き起こされる非決定性があります。したがって、副作用のある言語では、並列処理を実現する唯一の方法は並行性です。したがって、2つが混同していることがよくあるのは当然のことです。

FP評価順序

評価の順序は、機能構成の終了とパフォーマンスの副作用にも影響することに注意してください。

Eager(CBV)とLazy(CBN)は、カテゴリの決闘[ 10 ]です。これは、評価の順序が逆になっているためです。つまり、外部関数と内部関数のどちらが最初に評価されるかです。逆さまのツリーを想像してみてください。それから、eagerは、関数ツリーのブランチのヒントからブランチ階層のトップレベルの関数トランクまで評価します。一方、怠惰は幹から枝の先端まで評価します。Eagerは論理積(「and」、a / k / aカテゴリカル製品)を持たず、lazyは論理積(「or」、a / k / aカテゴリ「合計」)を持ちません[ 11 ]。

パフォーマンス

  • 熱心な

    非終了の場合と同様に、熱意は連言機能的構成では熱心すぎます。つまり、構成制御構造は、遅延では行われない不要な作業を行います。以下のため、熱心に熱心に不必要にそれが倍で構成されている場合、ブール値のリスト全体をマップする最初の真の要素で終了しています。

    この不要な作業は、両方とも純粋な関数を使用して、eagerとlazyのシーケンシャルな時間の複雑さで主張されている「最大」の余分なlog n係数の原因です。解決策は、遅延コンストラクター(つまり、オプションの遅延生成物を使用したい)でファンクター(リストなど)を使用することです。熱心さの不正確さは、内部関数に起因するためです。これは、積が構成型、つまり初期固定点に初期代数をもつ帰納型であるためです[ 11 ]

  • 怠惰な

    非終了の場合と同様に、lazyは選言的機能構成では怠惰です。つまり、共帰的ファイナリティは必要以上に遅くなる可能性があり、不要な作業と遅延の非決定性の両方が発生し、eager [ 10 ] [ 11 ] とは異なります。 。最終性の例は、状態、タイミング、非終了、および実行時例外です。これらは必須の副作用ですが、純粋な宣言型言語(Haskellなど)でも、空間の割り当てに暗黙の命令型IOモナド(注:すべてのモナドが命令型ではない!)に状態があり、タイミングは命令型に相対的な状態です現実の世界。オプションの熱心な副産物でもレイジーを使用すると、「怠惰」が内部の副産物にリークします。レイジーでは、遅延の不正確さが外部関数に起因するためです。(「非終了」セクションの例を参照してください。ここで==は外部の二項演算子関数です)。これは、副産物がファイナリティ、つまり最終オブジェクトに最終代数を伴う共帰型によって制限されるためです[ 11 ]。

    Lazyは、レイテンシとスペースの関数の設計とデバッグで不確定性を引き起こします。宣言された関数の階層と実行時の評価順序の不一致のため、そのデバッグはおそらく大多数のプログラマーの能力を超えています。熱心に評価された怠惰な純粋な関数は、実行時に以前見られなかった非終了を導入する可能性があります。逆に、lazyで評価される熱心な純粋関数は、実行時に以前には見えなかったスペースとレイテンシの不確定性をもたらす可能性があります。

非終了

コンパイル時に、チューリング完全言語での停止問題と相互再帰により、関数の終了は通常保証されません。

  • 熱心な

    熱心ではあるが怠惰ではなく、Head"and"の結合のTail場合、どちらHeadTailが終了しない場合、それぞれ左側List( Head(), Tail() ).tail == Tail()またはList( Head(), Tail() ).head == Head()右側が終了しないため、どちらかが真ではありません。

    一方、レイジーでは、両側が終了します。したがって、eagerは接続積ではあまりにも熱心であり、不要な場合は終了しない(ランタイム例外を含む)。

  • 怠惰な

    遅延ではあるが意欲的ではない場合、1"or"の分離のため、終了しない2場合、左側は終了し、右側はf終了しないList( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tailため、真ではありません。

    一方、eagerの場合、どちらの側も終了しないため、同等性テストに到達することはありません。したがって、lazyは選言的副産物ではあまりにも怠惰であり、そのような場合には、eagerの場合よりも多くの作業を行った後(ランタイム例外を含む)に失敗します。

[ 10 ]宣言の継続とカテゴリの双対性、Filinski、セクション2.5.4 SCLにおけるCBVとCBNの比較、および3.6.1 CBVとCBN。

[ 11 ]宣言的継続とカテゴリー的双対性、Filinski、セクション2.2.1製品と連産品、2.2.2最終オブジェクトと初期オブジェクト、2.5.2 CBVと遅延製品、および2.5.3 CBNと積極的な連産品。


宣言型制約プログラミングを使用しても、ソルバーがソリューションを見つけている間、制約は変化しません。変更する時間を指定する方法がないため、これは明らかです。他の制約に対して指定された制約でさえも、ソルバーが実行されて解を見つける前に記述されます。これは、スプレッドシートの宣言式に似ています。
シェルビームーアIII

3
省略は定義を提供することを意味しません。私が「RTはしばしば「副作用なし」と略される」と書いたが、それはRTの定義が「副作用なし」であることを意味するものではない。代わりに「RTはしばしば 'xyz'と略される」と言った場合、無意味なシンボルはRTを定義しません。RTは、それを参照するためにどの記号を使用しても、決して変更されない正確な定義持っています。
シェルビームーアIII

私はあらゆる種類のDPがRTであるという私の主張に対する反例を見つけることができません。たとえば、文脈依存文法の用語の意味(つまり値)は、文法内の別の時間または位置で変化しません。上記の制約プログラミングのコメントを参照してください。
シェルビームーアIII

1
等化ESPスタイルでC州RTとはモナドであり、無効な「内部の」状態は、それぞれ対応する文が生成モナド一方、各C文は、グローバル状態を変異させることができるので、COPY(SO修飾された)状態のを。後者はRTです-前者はそうではありません。モナディック構成は常に RTです。DP == RTは、属性のばらばらのセットであるDPの唯一の意味です(数学の証明は正しい、そうでなければDPは無意味です)。
シェルビームーアIII

1
これまでの最初の文が理解できたらいいのに。私はそれが「関数型言語」であることを示すDAXのマニュアルを読んでいました。これは何を意味するのでしょうか?あなたのポップに聞いてみませんか。
Nick.McDermaid 2016年

103

これらについては、明確で客観的な定義は実際にはありません。これががそれらを定義する方法です

必須 -焦点は、コンピュータが実行すること(例:C、C ++、Java)ではなく、コンピュータが実行する必要のあるステップにあります。

宣言 -焦点は、コンピュータが実行する方法(SQLなど)ではなく、コンピュータが実行する必要があることです。

関数型 -再帰に重点を置いた宣言型言語のサブセット


1
いくつかの点に注意してください。1)説明はすべてを網羅するのではなく単純にすることを目的としています。2)前述のように、これらの言語を定義するには複数の方法があります。したがって、答えはあなたと他の誰かにとって非常によく間違っているかもしれません。
Jason Baker、

3
関数型プログラミングは「宣言型言語のサブセット」ではありません。宣言型プログラミングは、格納された値の不変性を必要としますが、関数型プログラミングは、それが純粋な FP でない場合は不要です。私の答えをください。スプレッドシートのセル説明もご覧ください。正しい目的の定義は「あいまい」ではありません。命令型プログラミングも「コンピューターがすべきこと」に焦点を当てています。唯一の違いは、命令型プログラミングは、可変格納された値に対処しなければならないです。
シェルビームーアIII

5
@ShelbyMooreIII-私はこれについてエリック・マイヤーに同意する傾向があります。「純粋でない関数型言語」のようなものは実際にはありません。私に関する限り、Ocaml、F#などは、関数型データ構造を備えた命令型言語です。しかし、私の回答で述べたように、この質問には客観的で明確な回答があるとは思いません。物事を定義する方法はいくつかあります。
Jason Baker、

3
選択された属性は互いに素な集合ではないため、明確な定義がない場合は、彼が用語を融合していることを数学的に証明できます。FPを純粋な FP(つまりRT)としてのみ定義する場合、それはDPと区別されません。私の答え。FPのばらばらの属性には、必須関数である可能性があるファーストクラス関数タイプが含まれます。ここここ、より基本的な用語のあいまいさが見つかりまし純粋な FPを優先することは、FPのみの定義に直交します。
シェルビームーアIII

21
@ShelbyMooreIII-私はOPがMath Nerd-eseではなく英語で彼の答えを求めていると仮定していました。それが無効な仮定だった場合は、私の謝罪です。
Jason Baker

54

命令型宣言型は、2つの相反するプログラミングスタイルを記述しています。命令型は伝統的な「ステップバイステップのレシピ」アプローチであり、宣言型はより「これが私が欲しいものであり、今あなたはそれを行う方法を考え出す」です。

これら2つのアプローチは、同じ言語と同じプログラムであっても、プログラミング全体で発生します。プログラマーは多くの詳細を指定する必要がなく、バグの可能性も少ないため、一般的に宣言型のアプローチが望ましいと考えられています(必要な結果を記述し、十分にテストされた自動プロセスがそれから逆方向に機能する場合)ステップを定義すると、手作業で各ステップを指定するよりも信頼性が高まることが期待できます。

一方、命令型アプローチでは、より低レベルの制御が可能になります。これは、プログラミングに対する「マイクロマネージャーアプローチ」です。これにより、プログラマーは問題に関する知識を活用して、より効率的な答えを得ることができます。そのため、プログラムの一部の部分がより宣言的なスタイルで記述されることは珍しくありませんが、速度が重要な部分はより必須です。

ご想像のとおり、プログラムの記述に使用する言語は、どのように宣言できるかに影響を与えます-結果の説明から何をすべきかを判断するための組み込みの「スマート」を備えた言語は、はるかに宣言的なものになりますプログラマが最初にそのようなインテリジェンスを命令型コードで追加してから、より宣言的な層を上に構築できるようにする必要がある場合よりもアプローチします。したがって、たとえば、プロローグのような言語は、回答を検索するプロセスが組み込まれているため、非常に宣言的であると見なされます。

これまでのところ、関数型プログラミングについては触れていません。それは、その意味が他の2つと直接関係しない用語であるためです。最も単純な関数型プログラミングでは、関数を使用します。特に、「ファーストクラスの値」として関数をサポートする言語を使用すること。つまり、関数を作成できるだけでなく、関数を作成する(関数を作成する)関数を作成して、関数を渡すことができます。機能。つまり、その関数は文字列や数値のようなものと同じくらい柔軟で一般的です。

奇妙に思えるかもしれませんが、関数型、命令型、宣言型が一緒に言及されることがよくあります。これは、関数型プログラミングの考え方を「極端に」とらえた結果です。関数は、最も純粋な意味で、数学からの何か-ある入力を受け取り、常に同じ出力を与える一種の「ブラックボックス」です。そして、そのような振る舞いは変化する変数を保存することを必要としません。したがって、関数型プログラミングの非常に純粋で数学的な影響を受けたアイデアを実装することを目的とするプログラミング言語を設計する場合、(特定の、限定された、技術的な意味で)変化する可能性のある値のアイデアを主に拒否することになります。

そして、それを行う場合-変数の変更方法を制限すると-ほとんどの場合、命令型プログラミングの大部分が変数の変更方法を記述しているため、プログラマーはより宣言的なプログラムを作成しなければならず、もはやそれを行う!したがって、関数型プログラミング、特に関数型言語でのプログラミングは、より宣言的なコードを提供する傾向があることがわかります。

要約すると、次のようになります。

  • 命令型と宣言型は、2つの反対のプログラミングスタイルです(これらのスタイルを奨励するプログラミング言語には同じ名前が使用されます)。

  • 関数型プログラミングはプログラミングのスタイルであり、関数は非常に重要になり、結果として値の変更はそれほど重要ではなくなります。値の変更を指定する機能が制限されているため、より宣言的なスタイルが強制されます。

したがって、「関数型プログラミング」はしばしば「宣言型」と表現されます。


5
これまでの最高の説明。関数型とOOPは命令型と宣言型に直交しているようです。
Didier A.

論理プログラミングは宣言的だと思いますか?それとも直交していますか?
Didier A.

51

手短に:

命令型言語は、(それを行う、その後、これを行う)シーケンス内のコンピュータが実行することを一連の命令をspecfies。

宣言型言語は、出力が、そこからの入力(例。あなたがAを持っている場合、その結果はBである)結果としてどうあるべきかについてのルールのセットを宣言します。エンジンはこれらのルールを入力に適用し、出力を提供します。

関数型言語は、入力を出力に変換される方法を定義する数学的/論理関数のセットを宣言する。例えば。f(y)= y * y。それは一種の宣言型言語です。


1
関数型プログラミングは「宣言型言語の一種」ではありません。宣言型プログラミングでは、格納された値の不変性が必要ですが、純粋でない関数型プログラミングでは必要ありません。私の答えをください。スプレッドシートのセル説明もご覧ください。のみ(手順別名)必須論理シーケンスで実行される理由は、可変記憶された値が存在するため、結果は評価の順序に依存することです。語彙を使用すると、「命令」は可変値を操作できます(「ルール」はできません)。
シェルビームーアIII

23

必須:目標を達成する方法

   Take the next customer from a list.
   If the customer lives in Spain, show their details.
   If there are more customers in the list, go to the beginning

宣言型:何を私たちが達成したいです

   Show customer details of every customer living in Spain

関数型プログラミングと非FPではなく、宣言型プログラミングと命令型プログラミングではありません。関数型プログラミングは、命令型プログラミングと宣言型プログラミングの極性に直交しています。宣言型プログラミングでは、格納された値の不変性が必要ですが、純粋でない関数型プログラミングでは必要ありません。私の答えをください。
シェルビームーアIII

22

命令型プログラミングとは、コンピューターによって実行される操作がどのように行われるかを記述する命令からプログラムが構成されている、あらゆるスタイルのプログラミングを意味します。

宣言型プログラミングとは、プログラムが問題または解決策のいずれかの記述であるあらゆるスタイルのプログラミングを意味しますが、作業がどのように行われるかを明示的に述べてません

関数型プログラミングは、関数と関数の関数を評価することによるプログラミングです...(厳密に定義された)関数型プログラミングは、副作用のない数学関数を定義することによるプログラミングを意味するため、宣言型プログラミングの一種ですが、これは唯一の種類の宣言型プログラミングではありません

ロジックプログラミング(Prologなど)は、宣言型プログラミングの別の形式です。これには、論理ステートメントが真であるかどうか(またはそれが満たされるかどうか)を決定することによる計算が含まれます。プログラムは通常、一連の事実とルール、つまり一連の指示ではなく説明です。

用語書き換え(CASLなど)は、宣言型プログラミングの別の形式です。代数的用語の記号変換が含まれます。論理プログラミングや関数型プログラミングとは完全に異なります。


関数型プログラミングは「宣言型プログラミング」ではありません。宣言型プログラミングでは、格納された値の不変性が必要ですが、純粋でない関数型プログラミングでは必要ありません。私の答えをください。スプレッドシートのセル説明もご覧ください。「仕事の仕方を説明する」の「仕事」という用語は定義されていません。唯一の理由必須ロジック(別名「命令」)シーケンスで実行起因可変格納された値が存在するため、結果は評価の順序に依存することです。
シェルビームーアIII

2
私が純粋な関数型プログラミングについて話していたことを読んでください。これらのパラダイムは交差する可能性があり、私はハイブリッド言語と比較して行き詰まりたくありません。理論的には、少なくとも関数型プログラミングは、コンピューターが各計算を実行する方法を説明するのではなく、関数に関するものです。
Dafydd Rees

私は自分の回答を編集し、「関数型プログラミング」セクションで、FPは常に純粋であり、純粋でないFPは本当に「手続き型プログラミング」であると主張できるシナリオを追加しました。以前にその解釈を含めなかったことをお詫びします。
シェルビームーアIII

13

命令型 -式は実行する一連のアクションを記述します(連想)

宣言的 -式は、プログラムの動作に寄与する宣言です(連想、可換、べき等、単調)

関数型 -式には効果のみの価値があります。セマンティクスは方程式の推論をサポートします


1
宣言式はプログラムの意図された動作に寄与し、命令型は意図されたまたは意図されないものに寄与する可能性があります。宣言が意図的な意味論である場合、宣言は可換性およびべき等である必要はありません。機能の簡潔なエッセンスが好きなので、賛成しました。
シェルビームーアIII

10

以前の回答を書いてから、以下に引用する宣言的プロパティの新しい定義を作成しました。また、命令型プログラミングをデュアルプロパティとして定義しました。

この定義は簡潔であり、より一般的であるため、以前の回答で提供した定義よりも優れています。しかし、プログラミングや生活全般に当てはまる不完全性の定理の意味は人間が心を包み込むのが難しいため、理解するのはもっと難しいかもしれません。

引用されている定義の説明では、宣言型プログラミングで純粋関数型プログラミングが果たす役割について説明しています。

以下の定義はそれらが双対であると主張しているため、すべてのエキゾチックなプログラミングは、宣言と命令の次の分類に適合します。

宣言型と命令型

宣言的なプロパティは奇妙で鈍く、技術的に正確な定義で捉えることは困難であり、一般的で曖昧ではありません。これは、意図しない副作用を招くことなくプログラムの意味(別名セマンティクス)を宣言できるからです。意味の表現と意図しない影響の回避との間には固有の緊張があり、この緊張は実際にはプログラミングと宇宙の不完全性定理に由来しています。

これは、技術的に不正確な単純化、およびとして宣言型を定義することがしばしば曖昧である何をすべきかなどと不可欠どのように行います。あいまいなケースは、プログラムを出力するプログラム(コンパイラ)の「」が「どのように」であるです。

明らかに、チューリング言語を完全なものにする無制限の再帰も、評価の構文構造だけでなく、セマンティクスにも類似しています(操作上のセマンティクス)。これは論理的にはゲーデルの定理に類似した例です。「公理の完全なシステムにも一貫性がない」。その引用の矛盾した奇妙さを考えてください!したがって、我々は証明できない、また、意味論の発現が証明可能に拘束されていない方法を示す例である2を、そのプログラム(および同様にその意味論)の停止定理別名停止。

不完全性の定理は、熱力学の第2法則で述べられているように、「エントロピー(別名、独立した可能性の数)が永久に最大になる傾向にある」という宇宙の基本的な性質に由来します。プログラムのコーディングと設計は、現実世界のニーズに対処しようとするものであり、現実世界のセマンティクスは常に変化し、より多くの可能性に向かって流れているため、完成することはありません。人間は新しいもの(プログラムのエラーを含む;-)の発見を決して止めません。

エッジのないこの奇妙な宇宙の中でこの前述の望ましい概念を正確かつ技術的に捉えるには(つまり、宇宙の「外側」は存在しないことに注意してください)、簡潔でありながら一見単純ではない定義が必要です。深く。

定義:


宣言的なプロパティは、特定の各モジュールセマンティクスを表現できるステートメントの可能なセットが1つだけ存在できる場所です。

必須のプロパティ3は二重であり、セマンティクスは構成の下で一貫性がないか、ステートメントのセットのバリエーションで表現できます。


この宣言の定義は、セマンティックスコープで明確にローカルです。つまり、グローバルスコープでインスタンス化および使用される場所と方法に関係なく、モジュールセマンティックが一貫した意味を維持する必要があります。したがって、各宣言型モジュラーセマンティックは、すべての可能なその他-及びません(原因不完全定理に)不可能に本質的に直交性であるべき世界も「のポイントで一貫性を目撃するためのアルゴリズムやモデル、その他のではありません常により良いロバート・ハーパー、教授によります」標準MLの設計者の1人であるカーネギーメロン大学でコンピュータサイエンスの学位を取得しています。

これらのモジュール式の宣言的意味論の例としては、例えば、カテゴリ論ファンクタ含まれ、名目上のタイピングを、名前空間、名前付きフィールド、その後、意味論の運用レベルの純粋な関数型プログラミングにWRTを。Applicative

したがって、適切に設計された宣言型言語では、表現できるものの一般性がいくらか失われますが、本質的に一貫性をもって表現できるものの利点はありますが、より明確に意味を表現できます。

前述の定義の例は、スプレッドシートプログラムのセル内の数式のセットです。異なる列および行のセルに移動した場合、つまりセル識別子が変更された場合、同じ意味を示すことは期待されていません。セル識別子は意図された意味の一部であり、不要ではありません。したがって、各スプレッドシートの結果は、一連の数式のセル識別子に対して一意です。この場合の一貫したモジュラーセマンティクスは、セル数式の純粋関数の入力および出力としてセル識別子を使用することです(以下を参照)。

ハイパーテキストマークアップ言語(HTML(静的Webページ用の言語))は、(少なくともHTML 5以前は)動的な動作を表現する機能がなかった非常に(ただし完全ではない3)宣言型言語の例です。HTMLはおそらく学習が最も簡単な言語です。動的な動作の場合、JavaScriptなどの命令型スクリプト言語は通常、HTMLと組み合わされていました。JavaScriptを使用しないHTMLは宣言型の定義に適合します。これは、各名義型(つまり、タグ)が、構文の規則内の構成の下で一貫した意味を維持するためです。

宣言の競合する定義は、セマンティックステートメントの可換性とべき等のプロパティです。つまり、意味を変更せずにステートメントを並べ替えたり複製したりできます。たとえば、名前付きフィールドに値を割り当てるステートメントは、それらの名前が暗黙の順序でモジュール化されている場合、プログラムの意味を変更することなく、並べ替えたり複製したりできます。名前は時々順序を意味します。たとえば、セル識別子には列と行の位置が含まれます。スプレッドシートで合計を移動すると、その意味が変わります。それ以外の場合、これらのプロパティは暗黙的にグローバルを必要としますセマンティクスの一貫性。順序と複製はセマンティクスに固有であるため、ステートメントのセマンティクスを設計してランダムに順序付けまたは複製しても一貫性を保つことは、一般に不可能です。たとえば、「Fooが存在する」(または構文)および「Fooが存在しない」(および破壊)というステートメント。意図されたセマンティクスに固有のランダムな不整合を考慮すると、宣言的なプロパティにとって十分に一般的なものとしてこの定義を受け入れます。本質的に、この定義は一貫性をセマンティクスに直角にしようとする、つまりセマンティクスの宇宙は動的に無制限であり、グローバルコヒーレンスパラダイムでキャプチャできないという事実に対抗するため、一般化された定義として空虚です。

下位レベルの操作セマンティクス(の構造評価順序)の可換性とべき等のプロパティを要求すると、操作的セマンティクスが宣言的なローカライズされたモジュラーセマンティクス、たとえば純粋な関数型プログラミング(命令ループの代わりに再帰を含む)に変換されます。次に、実装の詳細の運用順序は、上位レベルのセマンティクスの一貫性に影響を与えません(つまり、グローバルに広がります)。たとえば、すべての出力が計算されるまで、つまり純粋な関数に類似するまで、出力は入力にコピーされないため、スプレッドシートの式の評価順序(および理論的には複製)は重要ではありません。

C、Java、C ++、C#、PHP、JavaScriptは特に宣言型ではありません。Coputeの構文とPythonの構文は、宣言的に意図した結果結合されます。つまり、無関係なものを排除する一貫した構文セマンティクスにより、コードを忘れた後でも簡単に理解できます。CoputeとHaskellは、運用上のセマンティクスの決定論を強制し、「自分自身を繰り返さないでください」(DRY)を奨励しています。


2たとえば、Coq言語を使用してプログラムのセマンティクスを証明できる場合でも、これはタイピングで表現されるセマンティクスに限定され、タイピングではプログラムのすべてのセマンティクスをキャプチャできません。完全なチューリングではありません。たとえば、HTML + CSSを使用すると、一貫性のない組み合わせを表すことができ、そのためセマンティクスが未定義になります。

3多くの説明では、命令型プログラミングのみが構文的に順序付けられたステートメントを持っていると誤って主張しています。命令型プログラミングと関数型プログラミングのこの混乱を明確にしました。たとえば、HTMLステートメントの順序は、その意味の一貫性を低下させません。


編集:ロバートハーパーのブログに次のコメントを投稿しました。

関数型プログラミングでは...変数の変動範囲はタイプです

関数型プログラミングと命令型プログラミングをどのように区別するかに応じて、命令型プログラムでの「割り当て可能」には、その可変性に限界を設ける型もある場合があります。

現在関数型プログラミングで高く評価されている唯一の混乱しない定義は、a)ファーストクラスのオブジェクトおよびタイプとしての関数、b)ループ上の再帰の優先、および/またはc)純粋な関数(つまり、目的のセマンティクスに影響を与えない関数)メモされたときのプログラムの(したがって、完全な純粋な関数型プログラミングは、メモリ割り当てなどの操作上のセマンティクスの影響により、汎用の意味論的なセマンティクスには存在しません)。

純粋な関数のべき等プロパティは、その変数に対する関数呼び出しをその値で置き換えることができることを意味します。これは、通常、命令手続きの引数には当てはまりません。純粋な関数は、入力タイプと結果タイプの間の構成されていない状態遷移に対して宣言的であるようです。

ただし、純粋な関数の構成では、このような一貫性は維持されません。これは、HaskellのIOMonadなどの純粋な関数型プログラミング言語で副作用(グローバル状態)命令型プロセスをモデル化することが可能であり、そのようなことを防ぐことは完全に不可能だからです。チューリングの完全な純粋関数型プログラミング言語。

2012年に書いたように、最近のブログでコメントのコンセンサスに似ているようですが、その宣言型プログラミングは、意図されたセマンティクスが決して不透明ではないという概念を捉えようとする試みです。不透明なセマンティクスの例は、順序への依存、操作上のセマンティクスレイヤーでの高レベルのセマンティクスの消去への依存(たとえば、キャストは変換ではなく、具体化されたジェネリックスは高レベルのセマンティクスを制限する)、チェックできない変数値への依存(証明される)正しい)プログラミング言語による。

したがって、Turing以外の完全な言語のみが宣言型であると結論付けました。

したがって、宣言型言語の明確で明確な属性の1つは、その出力が列挙可能な生成規則のセットに従うことが証明できることです。たとえば、スクリプトが記述されていない(つまり、チューリングが完了していない)特定のHTMLプログラム(インタープリターの分岐方法の違いを無視)の場合、その出力の変動性は列挙可能です。あるいはもっと簡単に言えば、HTMLプログラムはその可変性の純粋な機能です。同様に、スプレッドシートプログラムは、その入力変数の純粋な関数です。

したがって、宣言型言語は無限再帰正反対であるように思え ます。つまり、ゲーデルの2番目の不完全性定理ごとに、自己参照定理は証明できません。

Lesie Lamport 、ユークリッドがプログラミング言語の文脈で数学の証明に適用されたゲーデルの不完全性定理を、型と論理(カリーハワード対応など)の間で一致させることによってどのように処理したかについておとぎ話を書きました。


Robert Harperは、宣言型のほとんどの定義の無意味さについて私に同意しているようですが、彼が上記のものを見たことはないと思います。彼は表示の意味論について議論する私の定義に近づいていますが、彼は私の定義に到達していません。モデル(表示的意味論)は高レベルです
シェルビームーアIII

7

命令型プログラミング:何かを行う方法を「マシン」に伝えることで、結果として何が起こりたいかが起こります。

宣言型プログラミング:「マシン」に何を起こさせたいかを伝え、コンピュータにその方法を理解させる。

命令の例

function makeWidget(options) {
    const element = document.createElement('div');
    element.style.backgroundColor = options.bgColor;
    element.style.width = options.width;
    element.style.height = options.height;
    element.textContent = options.txt;

    return element;
}

宣言の例

function makeWidget(type, txt) {
    return new Element(type, txt);
}

注:違いは、簡潔さ、複雑さ、抽象化のいずれでもありません。述べたように、違いがあるどのようにするもの


2
良い例ですが、両方に少なくとも1つの例を提供する方が良いです。
Pardeep Jain、2016

4

今日、新しい焦点:古い分類が必要ですか?

命令型/宣言/機能性の面では、一般的な言語を分類するため、過去に良好であったが、今日ですべての「大言語」で、いくつかのオプション(典型的には(Javaの、Pythonのは、JavaScriptなど)フレームワーク「他の焦点」と表現する)をその主なもの(通常は必須)よりも、並列プロセス、宣言関数、ラムダなどを表現するため

したがって、この質問の良い変形は「今日のフレームワークを分類するためにどの側面が良いのか?」 ...重要な側面は、「プログラミングスタイル」とラベル付けできるものです...

データとアルゴリズムの融合に焦点を当てる

説明する良い例。ウィキペディアjQueryについて読むことができるように、

jQueryコア機能のセット— DOM要素の選択、トラバーサル、操作—は、そのセレクターエンジン(...)によって有効化され、新しい「プログラミングスタイル」、融合アルゴリズム、およびDOMデータ構造を作成しました

したがって、jQueryは「新しいプログラミングスタイル」に焦点を当てた最良の(人気のある)例です。これは、オブジェクト指向だけでなく、「アルゴリズムとデータ構造の融合 」です。jQueryは、スプレッドシートとしては多少反応しますが、「セル指向」ではなく、「DOMノード指向」です... このコンテキストでの主なスタイルの比較:

  1. 融合なし:すべての「大きな言語」、関数型/宣言型/命令型表現では、通常、データとアルゴリズムの「融合なし」ですが、オブジェクト指向を除き、厳密な代数構造の観点での融合です。

  2. いくつかの融合融合のすべての古典的な戦略、今日では、それをパラダイムとして使用するいくつかのフレームワークがあります... データフローイベント駆動型プログラミング(またはawkおよびXSLTのような古いドメイン固有の言語)...現代のスプレッドシートを使用したプログラミングと同様に、リアクティブプログラミングスタイルの例。

  3. 大きな融合:「jQueryスタイル」です... jQueryは、「アルゴリズムとDOMデータ構造の融合」に焦点を当てたドメイン固有の言語です。 PS:その他の「クエリ言語」、XQuery、SQL(命令式オプションとしてPLを使用)もデータアルゴリズムの融合の例ですが、他のシステムモジュールとの融合がないアイランドです...-バリアントを使用する場合の Spring仕様節は、別の良い融合の例です。
    find()


3

宣言型プログラミングとは、入力と出力の間に時間のないロジックを表現することによるプログラミングです。たとえば、擬似コードでは、次の例は宣言型になります。

def factorial(n):
  if n < 2:
    return 1
  else:
    return factorial(n-1)

output = factorial(argvec[0])

ここでは「階乗」と呼ばれる関係を定義し、出力と入力の関係をその関係として定義しています。ここで明らかなように、構造化言語については、宣言型プログラミングをある程度拡張することができます。宣言型プログラミングの中心的な考え方は不変データであり、変数に代入する場合、一度しか代入せず、二度と代入しません。その他のより厳密な定義では、副作用がまったくない可能性があります。これらの言語は、「純粋に宣言的」と呼ばれることもあります。

命令型の同じ結果は次のようになります。

a = 1
b = argvec[0]
while(b < 2):
  a * b--

output = a

この例では、入力と出力の間に時間のない静的な論理関係を表現せず、そのうちの1つが目的の結果を保持するまでメモリアドレスを手動で変更しました。すべての言語が宣言的意味論をある程度拡張できることは明らかですが、すべてが命令型を許可するわけではなく、一部の「純粋に」宣言型言語は副作用と変異を完全に許可します。

宣言型言語は、「実行方法」ではなく「実行する必要があること」を指定しているとよく言われますが、これは誤解です。宣言型プログラムでは、入力から出力への移動方法を指定していますが、別の方法では、指定する関係は効果的に計算可能である必要があります(重要な用語。わからない場合は調べてください)。別のアプローチは非決定的プログラミングです。これは、実装が成功するまで試行錯誤ですべてのパスを使い果たす前に、結果が満たす条件を実際に指定するだけです。

純粋な宣言型言語には、HaskellとPure Prologが含まれます。一方から他方へのスライディングスケールは、Pure Prolog、Haskell、OCaml、Scheme / Lisp、Python、Javascript、C-、Perl、PHP、C ++、Pascall、C、Fortran、Assemblyです。


関数型プログラミングを定義していません。宣言型プログラミングは不純である可能性があることを、「一部の「純粋な」宣言型言語」と誤って示唆しました。宣言型プログラミングでは、格納された値の不変性が必要ですが、命令型プログラミングでは必要ありません。私の答えをください。不変性は「時代を超えない」品質です。宣言factorialが値を変更しないことを確認してください。
シェルビームーアIII

3

言及された「タイプ」に関してここでいくつかの良い答え。

私は、関数型プログラミングの群集にしばしば関連する、より「エキゾチックな」追加の概念をいくつか提出します。

  • ドメイン固有言語またはDSLプログラミング:当面の問題に対処するための新しい言語の作成。
  • メタプログラミング:あなたのプログラムが他のプログラムを書くとき。
  • 進化的プログラミング:継続的に改善するシステムを構築するか、サブプログラムのより良い世代を継続的に生成するシステムを構築します。

3

あなたの分類法は間違っていると思います。命令型と宣言型の2つの反対のタイプがあります。関数型は宣言型のサブタイプにすぎません。ところで、ウィキペディアは同じ事実を述べています。


+1:うん、パラダイムはリンゴとオレンジです。
Nikhil Chelliah 2009年

FPは「宣言型の単なるサブタイプ」ではありません。FPは、命令対DPの極性と直交しています。DP 格納された値の不変性を必要としますが、純粋でないFPは必要としません。ウィキペディアは純粋な FPをFP と融合させています。次の概念は「一般に命令型プログラミングには無関係」であるという不条理な主張です。ファーストクラスの関数、再帰、評価順序、静的型付け。その後、ウィキペディアは不純な「非関数型言語での関数型プログラミング」を認めています。
シェルビームーアIII

ウィキペディアはこの点で間違っています。多くの一般的な関数型言語では、必要に応じて「宣言型」のプログラミングが可能ですが、宣言型言語ではありません。しかし、Cについても同じことが言えます。C*でも、必要に応じてvoid * sを使用して関数型のスタイルでプログラミングできます。
Plynx

おそらく、私はこの点についてもっと明確だったはずですが、反対側からは、あまり関係のない(imo)詳細でトピックスターターを台無しにするつもりはありません。関数型言語は宣言的に使用される傾向があることがわかります。ASMまたはCで宣言的または機能的に記述したり、おそらくLispで命令型プログラムを記述したりできますが、それが質問の著者にとって非常に役立つか、または有益であるとは思えません。したがって、本質的には、私は自分の答えを適切なものだと考えています。
Rorick

2

簡単に言えば、プログラミングスタイルが強調するほど(実行する方法)の詳細を抽象化して(実行する)方法を強調するほど、そのスタイルは宣言的であると見なされます。命令に対しては、反対のことが当てはまります。関数型プログラミングは宣言型スタイルに関連付けられています。


他の回答の下にある私のコメントを参照してください。FPは常に宣言的であるとは限りません。DPとIPのどちらにも、何をどのように関与するかというロジックがあるため、IPとDPの分類が間違っているのはどうしてですか。
シェルビームーアIII
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.