関数型プログラミングには不変性が本当に存在しますか?


9

私は日常生活でプログラマーとして働いており、流行の言語(Python、Java、Cなど)をすべて使用していますが、関数型プログラミングが何であるかについては、まだはっきりとはわかりません。私が読んだことから、関数型言語の1つの特性は、データ構造が不変であることです。私にとってこれだけでも多くの疑問を投げかけます。しかし、最初に、不変性について私が理解していることについて少し書きます。私が間違っている場合は、自由に修正してください。

不変性についての私の理解:

  • プログラムが起動すると、固定データを含む固定データ構造が含まれます
  • これらの構造に新しいデータを追加することはできません
  • コードに変数はありません
  • すでにデータまたは現在計算されているデータから単に「コピー」することができます
  • 上記すべてのため、不変性はプログラムに非常に大きなスペースの複雑さを追加

私の質問:

  1. データ構造がそのまま(不変)のままであると想定されている場合、リストに新しいアイテムを追加するにはどうすればよいですか?
  2. 新しいデータを取得できないプログラムがあることのポイントは何ですか?プログラムにデータを送りたいセンサーがコンピューターに接続されているとしましょう。これは、受信データをどこにも保存できないことを意味しますか?
  3. その場合、関数型プログラミングは機械学習にどのように役立ちますか?機械学習は、プログラムの「知覚」を更新するという仮定から構築されるため、新しいデータを保存します。

2
関数コードに変数がないと言うと、私はあなたに同意しません。「一連の値のいずれか1つをとる可能性のある量」の数学的意味の変数があります。確かにそれらは変更可能ではありませんが、数学ではどちらでもありません。
エドゥアール2015年

1
関数型言語について抽象的に考えているからです。Haskellの任意のプログラム(たとえば、コンソールから数値のリストを読み取り、それをすばやくソートして出力するプログラム)を使用して、それがどのように機能し、どのように疑いを否定するかを理解してください。哲学よりも実際のプログラムの例を見ずに、本当に物事を明確にする方法はありません。Haskellのチュートリアルには、たくさんのプログラムがあります。
jkff 2015

@jkff何を言おうとしているの?そのHaskelには非機能的な機能があります。問題はHaskellについてではなく、関数型プログラミングについてです。それとも、すべてが機能していると主張していますか?どうやって?それで、あなたが言うように、哲学で何が悪いのでしょうか。抽象化はどのように混乱しますか?OPの質問は非常に賢明です。
バブー2015

@babou純粋に関数型のプログラミング言語がアルゴリズムとデータ構造を効率的に実装する方法を理解する最善の方法は、関数型プログラミング言語で効率的に実装されたアルゴリズムとデータ構造の例を見ることです。OPはそれが概念的にどのように可能であるかを理解しようとしているように見えます。理解するための最も早い方法は、詳細にかかわらず、概念的な説明を読むのではなく、例を見ることです。
jkff 2015

関数型プログラミングを見る1つの方法は、副作用のないプログラミングであると言うことです。選択した「流行の」言語でそれを行うことができます。すべての再割り当てを避けないでください。たとえば、Javaでは、すべての変数がfinalになり、すべてのメソッドが読み取り専用になります。
reinierpost 2016

回答:


10

プログラムが起動すると、固定データを含む固定データ構造が含まれます

これは少し誤解です。固定形式と固定の一連の書き換えルールがありますが、これらの書き換えルールは、はるかに大きなものに爆発する可能性があります。たとえば、Haskellの式[1..100000000]は非常に少量のコードで表されますが、その通常の形式は巨大です。

これらの構造に新しいデータを追加することはできません

はいといいえ。HaskellやMLのような言語の純粋に機能的なサブセットは、外部の世界からデータを取得できませんが、実用的なプログラミング用の言語には、外部の世界から純粋に機能的なサブセットにデータを挿入するメカニズムがあります。Haskellではこれは非常に慎重に行われますが、MLではいつでも好きなときに行うことができます。

コードに変数はありません

これはかなり当てはまりますが、名前を付けることができないという考えと混同しないでください。有用な式に常に名前を付け、それらを常に再利用します。また、MLとHaskellの両方、私が試したすべてのLisp、そしてScalaのようなハイブリッドには、すべて変数を作成する手段があります。彼らは単に一般的に使用されていません。そして再び、そのような言語の純粋に機能的なサブセットはそれらを持っていません。

すでにデータまたは現在計算されているデータから単に「コピー」することができます

正規形に還元して計算できます。最善の方法は、関数型言語でプログラムを作成して、実際に計算を実行する方法を確認することです。

たとえば、「sum [1..1000]」は実行したい計算ではありませんが、Haskellによって非常に簡単に実行されます。私たちはそれに意味のある小さな表現を与え、Haskellは対応する数を私たちに与えました。だから、間違いなく計算を実行します。

データ構造がそのまま(不変)のままであると想定されている場合、リストに新しいアイテムを追加するにはどうすればよいですか?

リストに新しいアイテムを追加するのではなく、古いリストから新しいリストを作成します。古いものは変更できないので、新しいリストや他のどこでもそれを使用しても完全に安全です。このスキーマでは、より多くのデータを安全に共有できます。

新しいデータを取得できないプログラムがあることのポイントは何ですか?プログラムにデータを送りたいセンサーがコンピューターに接続されているとしましょう。これは、受信データをどこにも保存できないことを意味しますか?

ユーザー入力に関する限り、実用的なプログラミング言語にはユーザー入力を取得する方法があります。これが起こります。ただし、これらの言語の完全に機能するサブセットがあり、ほとんどのコードを記述して、この方法で利点を得ることができます。

その場合、関数型プログラミングは機械学習にどのように役立ちますか?機械学習は、プログラムの「知覚」を更新するという仮定から構築されるため、新しいデータを保存します。

これはアクティブラーニングの場合ですが、私が使用したほとんどの機械学習(私は機械学習グループでコードモンキーとして働いており、数年間これを行っています)には、すべてのトレーニングデータが読み込まれる1回の学習プロセスがありますすぐに。しかし、アクティブラーニングでは、100%純粋に機能的に物事を行うことはできません。外界からデータを読み込む必要があります。


@Pithikosの投稿で間違いなく最も重要な点、つまりスペースの問題である関数型プログラムは、命令型のものよりも多くのスペースを使用します(インプレースアルゴリズムなどは記述できません)
user541686

2
これは単に真実ではありません。ミューテーションの欠如は共有によって主に補われ、そのすべてを上回って、あなたが参照するサイズの違いは最近のコンパイラーでは驚くほど小さいです。haskellのリストのほとんどのコードは効率的に配置されているか、まったくメモリを使用しません。
Jake

1
MLをいくらか伝えていると思います。はい、I / Oはどこでも発生する可能性がありますが、新しい情報を既存の構造に導入する方法は厳密に制御されます。
dfeuer 2015年

@Pithikos、あちこちに変数があります。エドゥアールが示したように、慣れ親しんだものとはまったく異なります。そして、物事は継続的に割り当てられ、ガベージコレクションされます。実際に関数型プログラミングに入ると、実際にどのように機能するかをよりよく理解できます。
dfeuer 2015年

1
最もよく知られている命令型実装と同じ時間の複雑さを持つ純粋に機能的な実装を持たないアルゴリズムが存在することは事実です。たとえば、Union-Findデータ構造(および、ええと、配列:))複雑。しかし、これらは例外です。最も一般的なアルゴリズム/データ構造には、同等の時間と空間の複雑さを持つ実装があります。これは、プログラミングスタイルと(一定の要因により)コンパイラーの品質の主観的な問題です。
jkff 2015

4

不変性または可変性は、関数型プログラミングで意味のある概念ではありません。

計算コンテキスト

これは非常に良い質問であり、最近の別の質問に対する興味深いフォローアップです(重複ではありません)。割り当て、評価、および名前バインディングの違いは何ですか?

ここでは、あなたの発言に1つずつ返信するのではなく、問題となっていることの構造化された概要を説明します。

あなたに答えるために考慮すべきいくつかの問題があります。

  • 計算のモデルとは何か、そして与えられたモデルにとって意味のある概念は何か

  • あなたが使用している単語の意味は何ですか、それはどのように文脈に依存しますか

関数型プログラミングスタイルは、プログラマーの命令型の目で見るとばかげているように見えます。しかし、それは別のパラダイムであり、あなたの命令的な概念と知覚は場違いです。コンパイラーにはそのような偏見はありません。

しかし、最終的な結論は、機械学習を含む純粋に関数的な方法でプログラムを作成することが可能であり、関数型プログラミングにはデータを格納するという概念がないと考えたということです。この点については他の回答とは意見が一致しないようです。

この回答の長さにもかかわらず、少数の人が興味を示すことを期待しています。

計算パラダイム

問題は、計算の特定のモデルである関数型プログラミング(別名アプリケーションプログラミング)に関するもので、その理論的で最も単純な代表はラムダ計算です。

理論レベルに留まる場合、チューリングマシン(TM)、RAMマシンなど、ラムダ計算、組み合わせロジック、再帰関数理論、セミチューシステムなど、多くの計算モデルがあります。より強力な計算モデルは対応できる点で同等であることが証明されており、それがChurch-Turing論文の要点です 。

重要な概念は、モデルを互いに縮小することです。これは、チャーチチューリングの論文につながる同等性を確立するための基礎です。プログラマーの観点から見ると、1つのモデルを別のモデルに縮小することは、通常コンパイラーと呼ばれるものです。計算のモデルとして論理プログラミングを採用すると、店舗で購入したPCが提供するモデルとはかなり異なり、コンパイラは論理プログラミング言語で記述されたプログラムをPCが表す計算モデルに変換します(かなり多くの場合)。 RAMコンピュータ)。

β

実際には、私たちが使用するプログラミング言語は、理論的に異なる起源の概念を混合する傾向があり、プログラムの選択された部分が適切な場所にあるモデルのプロパティから利益を得ることができるようにしています。同様に、システムを構築する人々は、現在のタスクに言語を最適に合わせるために、異なるコンポーネントに異なる言語を選択する場合があります。

したがって、プログラミング言語の純粋な状態のプログラミングパラダイムはめったにありません。プログラミング言語は依然として主要なパラダイムに従って分類されていますが、他のパラダイムからの概念が含まれる場合、言語の特性が影響を受け、区別や概念的な問題が不明瞭になることがよくあります。

通常、HaskellやMLまたはCAMLなどの言語は関数型と見なされますが、命令型の動作を可能にすることができます...さもなければ、なぜ「純粋に関数型のサブセット」と言えるのでしょうか。

次に、これまたは私の関数型プログラミング言語でそれを行うことができると主張できますが、それは、関数型プログラミングに関する質問に実際に答えているわけではありません。

答えは、余分なものなしで、より正確に特定のパラダイムに関連している必要があります。

変数とは何ですか?

もう1つの問題は、用語の使用です。数学では、変数は、あるドメインで未決定の値を表すエンティティです。さまざまな目的で使用されます。方程式で使用される場合、方程式が検証されるような任意の値を表す場合があります。このビジョンは、「論理変数」という名前で論理プログラミングに使用されています。おそらく、変数の名前は、論理プログラミングの開発時にすでに別の意味を持っていたためです。

従来の命令型プログラミングでは、変数は値の表現を記憶し、場合によっては現在の値を別の値に置き換えることができるある種のコンテナー(またはメモリロケーション)として理解されます。

関数型プログラミングでは、変数は、数学では同じ値を、まだ提供されていない値のプレースホルダーとして使用します。従来の命令型プログラミングでは、この役割は実際には定数によって行われます(123、true、["abdcz"、3.14]などの値のドメインに固有の表記で表現された値を決定するリテラルと混同しないでください)。

定数だけでなく、あらゆる種類の変数は識別子で表すことができます。

命令型変数はその値を変更することができ、それが可変性の基礎となります。関数変数はできません。

プログラミング言語では通常、言語の小さいエンティティから大きいエンティティを構築できます。

命令型言語では、このような構成要素に変数を含めることができます。これにより、可変データが提供されます。

プログラムの読み方

プログラムは基本的に、実用的な設計であろうと、パラダイム的に純粋な言語であろうと、アルゴリズムが何らかの言語であるという抽象的な説明です。

原則として、抽象的に意味することが想定されているすべてのステートメントを使用できます。その後、コンパイラーはそれをコンピューターが実行する適切な形式に変換しますが、それは最初の近似では問題ではありません。

もちろん、現実は少し厳しく、コンパイラが効率的な実行のために対処する方法を知らない構造を回避するために、何が起こるかについてある程度の考えを持つことはしばしば良いことです。しかし、それはすでに最適化です...コンパイラーは非常に良い場合があり、多くの場合、プログラマーよりも優れています。

関数型プログラミングと可変性

変更可能性は、代入によって変更される、値を含むことができる命令型変数の存在に基づいています。これらは関数型プログラミングには存在しないため、すべてが不変であると見なすことができます。

機能プログラミングは、値のみを扱います。

不変性に関する最初の4つのステートメントはほとんどが正しいですが、必須ではない何かを必須のビューで説明します。それは、すべての人が盲目である世界で色で説明するようなものです。関数型プログラミングとは異なる概念を使用しています。

純粋な値のみがあり、整数の配列は純粋な値です。1つの要素のみが異なる別の配列を取得するには、別の配列値を使用する必要があります。要素の変更は、このコンテキストには存在しない概念にすぎません。配列といくつかのインデックスを引数として持つ関数があり、ほとんど同じ配列である結果を返す場合があります。この配列は、インデックスによって示される場所のみが異なります。しかし、それはまだ独立した​​配列値です。これらの値がどのように表されるかは問題ではありません。多分彼らはコンピュータの命令的な翻訳で多くの「共有」をします...しかし、それはコンパイラの仕事です...そしてあなたはそれがどのような種類の機械アーキテクチャをコンパイルしているかさえ知りたくありません。

値をコピーしません(意味がありません。それはエイリアンの概念です)。プログラムで定義したドメインに存在する値を使用するだけです。それらを(リテラルとして)記述するか、関数を他のいくつかの値に適用した結果です。それらに名前を付けて(したがって定数を定義する)、同じ値がプログラムの別の場所で使用されるようにすることができます。関数適用は計算としてではなく、与えられた引数への適用の結果として認識されるべきであることに注意してください。書き込み5+2または書き込み7は同じです。これは前の段落と一致しています。

必須の変数はありません。割り当てはできません。割り当て可能な変数に名前をバインドできる命令型言語とは異なり、(定数を形成するために)名前のみを値にバインドできます。

それが複雑さを犠牲にするかどうかは完全に不明です。1つには、複雑さは命令パラダイムに関係していることを参照します。関数型プログラミングでは、関数型プログラムを命令型プログラムとして読むことを選択した場合を除き、そのように定義されていません。これは、設計者の意図ではありません。実際、機能ビューは、そのような問題を心配せず、計算されているものに集中できるようにすることを目的としています。それは仕様と実装のようなものです。

コンパイラーは実装を管理し、実行するハードウェアが何であれ、実行するハードウェアに最適化できるように十分にスマートでなければなりません。

プログラマがそんなことを心配する必要がないと言っているのではありません。また、プログラミング言語やコンパイラテクノロジが、私たちが望むほど成熟していると言っているわけではありません。

質問に答える

  1. 既存の値(エイリアンの概念)を変更するのではなく、必要に応じて異なる新しい値を計算します。おそらく、それがリストである追加の要素を1つ持つことによってです。

  2. プログラムは新しいデータを取得できます。重要なのは、それを言語でどのように表現するかです。たとえば、プログラムが1つの特定の値(おそらくサイズに制限がない)で機能することを考慮することができます。これは入力ストリームと呼ばれます。それはそこに座っているはずの値です(すでに完全にわかっているかどうかは問題ではありません)。次に、ストリームの最初の要素と残りのストリームで構成されるペアを返す関数があります。

    これを使用して、純粋に応用的な方法でコミュニケートするコンポーネントのネットワークを構築できます(コルーチン)

  3. 機械学習は、データを追加して値を変更する必要がある場合のもう1つの問題です。関数型プログラミングではこれを行わず、トレーニングデータに応じて適切に異なる新しい値を計算するだけです。結果のマシンも動作します。心配しているのは、時間とスペースの効率の計算です。しかし、これもまた別の問題であり、理想的にはコンパイラーが対処する必要があります。

最後の発言

コメントやその他の回答から、実用的な関数型プログラミング言語は純粋に関数型ではないことは明らかです。これは、特にコンパイルに関しては、当社のテクノロジーがまだ改善されていないという事実を反映しています。

純粋に応用的なスタイルで書くことは可能ですか?答えは約40年前から知られており、「はい」です。1970年代に登場した表記セマンティクスのまさに目的は、言語を純粋に機能的なスタイルに翻訳(コンパイル)することであり、数学的に理解が深まり、プログラムのセマンティクスを定義するためのより良い資金と考えられていました。

興味深い点は、変数を含む命令型プログラミング構造は、データストアなどの値の適切なドメインを導入することにより、関数スタイルに変換できることです。そして、関数型のスタイルにも関わらず、命令型で書かれた実際のコンパイラーのコードに驚くほど似ています。


0

関数型プログラムはデータを格納できないというのは誤解です。ジェイクスの回答がこれを非常によく説明したとは思いません。

関数型プログラムは、他のプログラムと同様に、整数を整数にマッピングする関数です。可変データ構造で動作する命令型プログラムには、対応する機能があります。これは、同じ目的を達成するためのもう1つの手段です。

あるソースからの実験データを保存する機能的な方法は、データ構造を引数として保存関数を呼び出し、既存のデータ構造と新しいデータの連結を出力することです。したがって、データは変更可能なデータ構造の概念なしで保存されます。

私自身の経験から、不変のデータ構造の概念は、従来の開発者が機能的な設定で非現実的または不可能でさえある特定のことを考えるように導いていると思います。これはそうではありません。


「関数型プログラムは、他のプログラムと同様に、整数を整数にマッピングする関数です。」たとえば、実際にMinecraftは整数を整数にマッピングする関数ですか?
David Richerby、2015

簡単に。すべてのバイトは2進整数として解釈できます。コンピュータの状態は、バイトのコレクションです。Minecraftでさえも、プログラムはコンピューターの状態を操作し、ある状態から別の状態にマッピングします。
Jeppe Hartmund、2015

ユーザー入力がこの世界に適合していないようです。
David Richerby、2015

ユーザー入力はコンピューターの状態の一部です。画面上に存在するだけではありません。
Jeppe Hartmund、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.