関数型プログラミング言語を命令型ではなく宣言型にするのはなぜですか?


17

関数型プログラミングの利点を説明する多くの記事で、C / C ++ / C#/ Javaなどの命令型言語とは異なる「宣言型言語」と呼ばれるHaskell、ML、Scala、Clojureなどの関数型プログラミング言語を見てきました。私の質問は、関数型プログラミング言語を命令型ではなく宣言型にするものです。

宣言型プログラミングと命令型プログラミングの違いを説明するよくある説明は、命令型プログラミングでは、宣言型言語の「何をすべきか」ではなく「何をするか」をコンピューターに伝えるというものです。この説明に関して私が抱えている問題は、すべてのプログラミング言語で常に両方を実行しているということです。最下位レベルのアセンブリに移動しても、コンピューターに「何をすべきか」と伝え、CPUに2つの数字を追加するように指示します。追加の実行方法については指示しません。Haskellのような高レベルの純粋な関数型言語であるスペクトルのもう一方の端に行くと、実際には特定のタスクを達成する方法をコンピューターに伝えていることになります。それはあなたのプログラムがコンピューターが単独で達成する方法を知らない特定のタスクを達成するための一連の命令です。Haskell、Clojureなどの言語はC / C ++ / C#/ Javaより明らかに高いレベルであり、遅延評価、不変データ構造、匿名関数、カリー化、永続データ構造などの機能を提供することを理解しています関数型プログラミングは可能かつ効率的ですが、宣言型言語として分類しません。

私にとって純粋な宣言型言語は、宣言のみで構成される言語であり、そのような言語の例はCSSです(はい、CSSは技術的にはプログラミング言語ではないことを知っています)。CSSには、ページのHTMLおよびJavascriptで使用されるスタイル宣言が含まれています。CSSは宣言以外に何もできません。クラス関数、つまり、パラメーターに基づいて表示するスタイルを決定する関数、CSSスクリプトを実行できない関数などは作成できません。プログラミング言語)。

更新:

私は最近Prologをいじくり回してきましたが、完全に宣言的なプログラミング言語だけではない場合、Prologは完全に宣言的な言語(少なくとも私の意見では)に最も近いプログラミング言語です。Prologでプログラミングを精緻化するには、ファクト(特定の入力に対してtrueを返す述語関数)またはルール(入力に基づいて特定の条件/パターンに対してtrueを返す述語関数)のいずれかを宣言することによって行われます。パターンマッチング手法を使用して定義されます。プロローグで何かを行うには、述語の1つ以上の入力を変数に置き換えてナレッジベースを照会し、プロローグが述語が成功する変数の値を見つけようとします。

私のポイントは、プロローグには命令的な指示はなく、基本的にはコンピューターに知っていることを伝え(宣言)、知識について尋ねる(問い合わせる)ことです。関数型プログラミング言語では、メモリの場所を直接操作したり、段階的に計算を書き込んだりしていなくても、値の取得、関数Xの呼び出し、それに1の追加などの指示を与えています。私は間違っているかもしれませんが、Haskell、ML、Scala、Clojureでのプログラミングがこの意味で宣言的であるとは言いません。上で説明した意味で、適切で、真で、純粋な関数型プログラミング宣言です。


彼が必要なとき、@ JimmyHoffaはどこにありますか?

2
1. C#と最新の C ++は非常に機能的な言語です。2.同様の目的を達成するために、SQLとJavaを書くことの違いを考えてください(おそらく、結合を伴う複雑な照会)。また、C#のLINQが単純なforeachループの記述とどのように異なるかを考えることもできます
。– AK_

また、違いは明確に定義されたものよりもスタイルの問題です...プロローグを使用している場合を除き
...-AK_

1
違いを認識するための鍵の1つは、代入演算子と定義/宣言演算子を使用している場合です。たとえば、Haskellには代入演算子はありません。これら2つの演算子の動作は非常に異なり、コードの設計を強制します。
ジミー・ホッファ

1
残念ながら、割り当て演算子がなくても、私はこれを行うことができます(let [x 1] (let [x (+ x 2)] (let [x (* x x)] x)))(ご理解いただければ幸いです、Clojure)。私の最初の質問は、これがこのintと何が違うのかということx = 1; x += 2; x *= x; return x;です。私の意見では、ほとんど同じです。
ALXGTV

回答:


16

あなたは物事の宣言と機械の指示の間に線を引いているようです。そのようなハードと高速の分離はありません。命令型プログラミングで指示されるマシンは物理的なハードウェアである必要はないため、解釈の自由度が非常に高くなります。ほとんどすべては、適切な抽象マシン用の明示的なプログラムと見なすことができます。たとえば、CSSは、主にセレクターを解決し、こうして選択されたDOMオブジェクトの属性を設定するマシンをプログラミングするための、かなり高レベルの言語として見ることができます。

問題は、そのような観点が賢明であるかどうか、逆に、命令のシーケンスが計算される結果の宣言にどれだけ似ているかです。CSSの場合、宣言的観点が明らかに便利です。Cにとっては、命令的な視点が明らかに支配的です。Haskellなどの言語については…

言語は、具体的なセマンティクスを指定しています。つまり、もちろんプログラムを一連の操作として解釈できます。プリミティブな操作を選択して、市販のハードウェアにうまくマッピングできるようにするのも、それほど労力はかかりません(これは、STGマシンや他のモデルが行うことです)。

ただし、Haskellプログラムの作成方法では、計算される結果の説明として頻繁に賢明に読み取ることができます。たとえば、最初のN個の階乗の合計を計算するプログラムを考えます。

sum_of_fac n = sum [product [1..i] | i <- ..n]

これを解糖してSTG操作のシーケンスとして読むことができますが、結果の説明として読む方がはるかに自然です(つまり、「計算するもの」よりも宣言型プログラミングのより有用な定義です):結果は、[1..i]for all i= 0、…、n の積の合計です。そして、それはほとんどすべてのCプログラムや関数よりもはるかに宣言的です。


1
私がこの質問をした理由は、C / C ++ / C#/ JavaまたはPythonのパラダイムから完全に分離されたいくつかの新しい別個のパラダイムとして機能プログラミングを促進しようとする人が多いということです。命令型プログラミング。
ALXGTV

ここで私が意味することを説明するには、sum_of_facを定義していますが、sum_of_facはそれがあなたのホールアプリケーションである場合を除き、それ自体ではほとんど役に立たない、あなたは最終的に特定のタスクを達成するために構築する他の結果を計算するために結果を使用する必要がありますプログラムが解決するように設計されたタスク。
ALXGTV

6
@ALXGTV関数型プログラミング非常に異なるパラダイムです。たとえそれを命令型として読むことに固執していても(それは非常に広範な分類であり、プログラミングパラダイムにはそれ以上のものがあります)。また、このコードに基づいて構築される他のコードも宣言的に読み取ることができますmap (sum_of_fac . read) (lines fileContent)。たとえば、もちろん、ある時点でI / Oが機能しますが、先ほど言ったように、それは連続です。Haskellプログラムの一部は、Cがsorta宣言式構文(x + y、not load x; load y; add;)を持っているのと同じように、より命令的です、確かに

1
@ALXGTVあなたの直感は正しいです。宣言型プログラミング「ジャスト」は、特定の命令の詳細よりも高いレベルの抽象化を提供します。これは大したことだと思います!
アンドレスF.

1
@Brendan関数型プログラミングは命令型プログラミングのサブセットではないと思います(FPの不変性は必須の特性でもありません)。純粋に静的に型付けされたFPの場合、それは型の効果が明示的な命令型プログラミングのスーパーセットであると主張できます。あなたは、Haskellが「世界で最も優れた命令言語」であるという舌に負けた主張を知っています;)
Andres F.

21

命令型プログラムの基本単位はステートメントです。ステートメントは副作用のために実行されます。受け取る状態を変更します。一連のステートメントは一連のコマンドであり、これを実行してから実行することを示します。プログラマは、計算を実行する正確な順序を指定します。これは、コンピューターにその方法を伝えることによって人々が意味するものです。

宣言型プログラムの基本単位はです。式には副作用がありません。入力と出力の間の関係を指定し、入力状態を変更するのではなく、入力から新しい別個の出力を作成します。式のシーケンスは、それらの関係を指定する式を含むものがなければ意味がありません。プログラマはデータ間の関係を指定し、プログラムはそれらの関係から計算を実行する順序を推測します。これは、コンピュータにをすべきを伝えることによって人々が意味するものです。

命令型言語には式がありますが、物事を成し遂げる主な手段はステートメントです。同様に、宣言型言語には、Haskellのdo表記法内のモナドシーケンスのようなステートメントに類似したセマンティクスを持ついくつかの式がありますが、それらの中核は、1つの大きな式です。これにより、初心者は非常に命令的な外観のコードを作成できますが、そのパラダイムから逃れると、言語の真の力が発揮されます。


1
例が含まれている場合、これは完璧な答えになります。宣言的対命令的を説明するために私が知っている最良の例は、Linq対foreachです。
ロバートハーヴェイ

7

宣言型プログラミングを命令型プログラミングから分離する実際の定義特性は、シーケンス命令を与えない宣言型スタイルです。下位レベルでは、はいCPUはこのように動作しますが、それはコンパイラーの懸念です。

CSSは「宣言型言語」であることをお勧めします。CSSを言語とはまったく呼びません。JSON、XML、CSV、またはINIなどのデータ構造形式であり、何らかの性質のインタープリターに知られているデータを定義するための単なる形式です。

言語から代入演算子を取り出すと、いくつかの興味深い副作用が発生します。これが、宣言型言語でのstep1-step 2-step 3命令命令をすべて失う本当の原因です。

関数で代入演算子を使用して何をしますか?中間ステップを作成します。それが核心です。代入演算子を使用して、データを段階的に変更します。データを変更できなくなるとすぐに、これらの手順がすべて失われ、次のようになります。

すべての関数には1つのステートメントのみがあり、このステートメントは単一の宣言です

今、単一のステートメントを多くのステートメントのように見せるための多くの方法がありますが、それは単なる策略です。たとえば、1 + 3 + (2*4) + 8 - (7 / (8*3))これは明らかに単一のステートメントですが、次のように書くと...

1 +
3 +
(
  2*4
) +
8 - 
(
  7 / (8*3)
)

それは、作者が意図していた望ましい分解を脳が認識しやすい一連の操作の出現を引き受けることができます。私はよくC#で次のようなコードを使ってこれを行います。

people
  .Where(person => person.MiddleName != null)
  .Select(person => person.MiddleName)
  .Distinct();

これは単一のステートメントですが、多くに分解されているように見えます-割り当てがないことに注意してください。

また、上記の両方の方法で、コードが実際に実行される方法は、すぐに読み込む順序ではないことに注意してください。このコードは何をすべきかを指示しますが、どのように指示するのではないためです。上記の単純な算術演算は明らかに左から右に書かれたシーケンスでは実行されないことに注意してください。上記のC#の例では、これらの3つのメソッドは実際にはすべてリエントラントであり、シーケンスで最後まで実行されず、コンパイラーは実際にコードを生成しますそれはその文が望むことをしますが、必ずしもあなたが仮定する方法ではありません。

宣言型コーディングのこの中間段階のないアプローチに慣れることは、本当に最も難しい部分だと思います。そして、Haskellのように実際に許可しない言語はほとんどないため、Haskellが非常に難しい理由です。合計などの中間変数を使用して通常行うことを達成するために、いくつかの興味深い体操を開始する必要があります。

宣言型言語では、中間変数に何かをさせたい場合、それを、それを行う関数にパラメーターとして渡すことを意味します。これが、再帰が非常に重要になる理由です。

sum xs = (head xs) + sum (tail xs)

あなたは、作成することはできませんresultSum変数をしてて、あなたループとしてそれに追加するxsと、他のアクセスすべてにお渡しする必要があります-あなたは、単に最初の値を取り、合計は他のすべてのあるものは何でも、それを追加する必要があり、xs関数にtailするので単にxsの変数を作成し、命令型のアプローチになるはずの頭を飛び越えることはできません。(はい、あなたは破壊を使用できることを知っていますが、この例は例示を目的としています)


あなたの答えから私が理解する方法は、純粋な関数型プログラミングでは、プログラム全体が多くの単一式関数で構成されているのに対し、命令型プログラミング関数(手順)では、定義された操作シーケンスを持つ複数の式が含まれていることです
ALXGTV

1 + 3 +(2 * 4)+ 8-(7 /(8 * 3))実際には複数のステートメント/式です。もちろん、単一の式として見るものに依存します。それを要約すると、関数型プログラミングは基本的にセミコロンなしのプログラミングです。
ALXGTV

1
@ALXGTVその宣言と命令型スタイルの違いは、その数式がステートメントである場合、記述どおりに実行することが必須であることです。ただし、命令文のシーケンスではなく、必要なものを定義する式であるため、howとは言わないため、記述どおりに実行することは必須ではありません。したがって、コンパイルは式を減らすか、リテラルとしてメモすることさえできます。命令型言語もこれを行いますが、単一のステートメントに記述した場合のみです。宣言。
ジミーホファ

@ALXGTV宣言は関係を定義し、関係は順応性があります。もしx = 1 + 2その後、x = 3x = 4 - 1。シーケンス化されたステートメントは命令を定義するため、x = (y = 1; z = 2 + y; return z;)順応性のないもの、コンパイラーが複数の方法で実行できることはなくなります-コンパイラーは、命令のすべての副作用を知ることができないため、そこで指示したとおりに実行する必要がありますそれらを変更します。
ジミー・ホッファ

公平な点があります。
ALXGTV

6

私はパーティーに遅れていることは知っていますが、先日はひらめきがあったのでここに行きます...

関数型、不変型、副作用なしについてのコメントは、宣言型と命令型の違いを説明したり、宣言型プログラミングの意味を説明したりすると、マークを逃します。また、あなたが質問で言及したように、「何をするか」対「それを行う方法」全体はあまりに曖昧であり、実際にも説明していません。

簡単なコードa = b + cを基礎として、いくつかの異なる言語でステートメントを表示して、アイデアを理解しましょう。

a = b + cCのような命令型言語で書くとき、変数に現在の値を割り当てます。私たちは何について基本的な声明を出していません。むしろ、プロセスのステップを実行するだけです。b + caa

私たちが書くときa = b + c宣言型言語では、Microsoft Excelの、(はい、Excelが似ている、プログラミング言語とそれらすべてのおそらく最も宣言型)我々はしている関係アサートの間をabそしてcそれは常にそうであるようにaとの和であります他の2つ。それはプロセスのステップではなく、不変式、保証、真実の宣言です。

関数型言語も宣言的ですが、ほとんど偶然です。たとえば、Haskelではa = b + c不変の関係もアサートしますが、これはband cが不変であるためです。

そのため、オブジェクトが不変であり、関数に副作用がない場合、コードは宣言型になります(たとえ命令型コードと同じように見えても)、それらはポイントではありません。また、割り当てを回避していません。宣言型コードのポイントは、リレーションシップに関する基本的なステートメントを作成することです。


0

コンピューターにをするか、どのように行うを明確に区別することはできません。

ただし、スペクトルの片側では、メモリの操作方法についてほぼ排他的に考えます。つまり、問題を解決するには、「このメモリの場所をxに設定し、そのメモリの場所をyに設定し、メモリの場所zにジャンプする...」などの形式でコンピュータに提示します。結果は他のメモリ位置にあります。

Java、C#などのマネージ言語では、ハードウェアメモリに直接アクセスできなくなりました。命令型プログラマーは現在、静的インスタンス、参照、またはクラスインスタンスのフィールドに関心を持っています。これらはすべて、メモリの場所をある程度欠いています。

Haskell、OTOHなどの言語では、記憶は完全になくなります。それは単にそうではありません

f a b = let y = sum [a..b] in y*y

引数aとbを保持する2つのメモリセルと、中間結果yを保持する別のメモリセルが必要です。確かに、コンパイラバックエンドは、この方法で動作する最終コードを生成できます(ターゲットアーキテクチャがv。Neumannマシンである限り、何らかの意味でそうする必要があります)。

しかし、ポイントは、上記の機能を理解するためにv。Neumannアーキテクチャを内部化する必要がないことです。また、それを実行するための現代的なコンピューターも必要ありません。たとえば、純粋なFP言語のプログラムを、SKI計算に基づいて動作する仮想マシンに簡単に変換できます。Cプログラムでも同じことを試してください!

私にとって純粋な宣言型言語は、宣言のみで完全に構成される言語です。

これは十分ではありません、私見。Cプログラムでさえ、宣言のシーケンスにすぎません。宣言をさらに修飾する必要があると思います。たとえば、彼ら何か(宣言的)またはそれ何をするか(命令的)を教えてくれますか。


関数型プログラミングはCでのプログラミングと変わらないとは言いませんでした。実際、Haskellなどの言語はCよりもはるかに高いレベルにあり、抽象化がはるかに大きいと言いました。
ALXGTV
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.