web-dev-qa-db-ja.com

オブジェクトが可変である場合、関数型プログラミングのコンテキストで何が問題になる可能性がありますか?

不変オブジェクトのような可変オブジェクトと不変オブジェクトのメリットは、共有および書き込み可能な状態が原因で、マルチスレッドプログラミングの問題のトラブルシューティングが非常に困難になることを理解できます。逆に、変更可能なオブジェクトは、毎回新しいコピーを作成するのではなく、オブジェクトのIDを処理するのに役立ちます。そのため、特に大きなオブジェクトのパフォーマンスとメモリ使用量も向上します。

私が理解しようとしていることの1つは、関数型プログラミングのコンテキストで可変オブジェクトを使用するときに何が問題になるかです。私に言われた点の1つのように、異なる順序で関数を呼び出した結果は確定的ではありません。

関数プログラミングで可変オブジェクトを使用すると何がうまくいかないかが非常に明白な実際の具体例を探しています。基本的にそれが悪い場合は、OOまたは関数型プログラミングのパラダイムに関係なく、悪いですか?

私自身の声明自体がこの質問に答えると思います。しかし、それでももっと自然に感じられるように、いくつかの例が必要です。

OOは、カプセル化、ポリモーフィズムなどのツールを使用して、依存関係を管理し、より簡単で保守可能なプログラムを作成するのに役立ちます。

関数型プログラミングも保守可能なコードを促進する同じ動機を持っていますが、OOツールとテクニックを使用する必要性を排除するスタイルを使用することによって-その1つは副作用、純粋な関数などを最小化することによると私は信じています。

9
rahulaga_dev

OOアプローチと比較することによって、重要性が最もよく示されると思います

たとえば、オブジェクトがあるとしましょう

Order
{
    string Status {get;set;}
    Purchase()
    {
        this.Status = "Purchased";
    }
}

OOパラダイムでは、メソッドはデータに関連付けられており、そのデータがメソッドによって変更されることは理にかなっています。

var order = new Order();
order.Purchase();
Console.WriteLine(order.Status); // "Purchased"

関数型パラダイムでは、関数の観点から結果を定義します。購入した注文[〜#〜] is [〜#〜]注文に適用された購入機能の結果。これは私たちが確認する必要があるいくつかのことを意味します

var order = new Order(); //this is a 'new order'
var purchasedOrder = purchase(order); // this is a 'purchased order'
Console.WriteLine(order.Status); // "New" order is still a 'new order'

Order.Status == "Purchased"を期待しますか?

また、関数がべき等であることも意味します。すなわち。それらを2回実行すると、毎回同じ結果が生成されます。

var order = new Order(); //new order
var purchasedOrder = purchase(order); //purchased order
var purchasedOrder2 = purchase(order); //another purchased order
var purchasedOrder = purchase(purchasedOrder); //error! cant purchase an order twice

Purchase関数によって注文が変更された場合、purchaseOrder2は失敗します。

物事を関数の結果として定義することにより、実際に計算せずにそれらの結果を使用することができます。プログラミング用語でどれが遅延実行です。

これはそれ自体で便利ですが、関数が実際にいつ発生するかわからない場合、およびそれについて問題がなければ、OOパラダイムよりもはるかに多くの並列処理を活用できます。 。

関数を実行しても、別の関数の結果には影響しないことがわかっています。そのため、コンピューターを好きなだけスレッドを使用して、選択した任意の順序で実行することができます。

関数がその入力を変更する場合、そのようなことについてもっと注意する必要があります。

7
Ewan

不変オブジェクトが有益である理由を理解するための鍵は、実際には関数コードで具体的な例を見つけることではありません。ほとんどの関数型コードは関数型言語を使用して記述されており、ほとんどの関数型言語はデフォルトで不変であるため、パラダイムの本質は、探しているものが起こらないように設計されています。

質問する重要なことは、不変性の利点は何ですか?答えは、複雑さを回避することです。 2つの変数xyがあるとします。どちらも1の値で始まります。 yは13秒ごとに2倍になります。それらのそれぞれの価値は、20日間でどのようになりますか? x1になります。簡単だ。ただし、yの方が複雑なので、解決するには努力が必要です。 20日間の何時ですか?夏時間を考慮する必要がありますか? yxの複雑さは、はるかに複雑です。

そして、これは実際のコードでも発生します。変化する値をミックスに追加するたびに、コードを記述、読み取り、またはデバッグしようとするときに、頭の中で、または紙の上で保持および計算するための別の複雑な値になります。複雑さが増すほど、間違いを犯し、バグが発生する可能性が高くなります。コードを書くのは難しいです。読みにくい;デバッグが難しい:コードを正しく理解するのが難しい。

可変性はbadではありません。ミュータビリティがゼロのプログラムでは結果が得られない可能性があり、これはほとんど役に立ちません。可変性が結果を画面やディスクなどに書き込むことであっても、そこに存在する必要があります。悪いことは、不必要な複雑さです。複雑さを軽減する最も簡単な方法の1つは、パフォーマンスまたは機能上の理由から、デフォルトで物事を不変にし、必要な場合にのみ変更可能にすることです。

12
David Arno

関数型プログラミングのコンテキストで何が問題になるか

非関数型プログラミングで問題が発生する可能性があるのと同じこと:不要な予期しない 副作用を取得する可能性があります。スコープ付きプログラミング言語の発明以来、よく知られたエラーの原因。

これが関数型プログラミングと非関数型プログラミングの唯一の本当の違いは、非関数型コードでは通常、副作用が予想されますが、関数型プログラミングではそうではありません。

基本的にそれが悪い場合、OOや関数型プログラミングのパラダイムに関係なく、悪いのですよね?

確かに-パラダイムに関係なく、不要な副作用はバグのカテゴリです。反対も同様です-意図的に使用された副作用はパフォーマンスの問題への対処に役立ち、I/Oや外部システムへの対処に関してはほとんどの実世界のプログラムに必要です-パラダイムに関係なく。

8
Doc Brown

私はちょうどあなたの質問をかなりよく説明する StackOverflow質問 に答えました。可変データ構造の主な問題は、IDが特定の瞬間にのみ有効であるため、IDが一定であることがわかっているコードの小さなポイントにできるだけ多くのことを詰め込む傾向があることです。この特定の例では、forループ内で多くのログを記録しています。

for (elem <- rows map (row => s3 map row)) {
  val elem_str = elem.map(_.toString)

  logger.info("verifying the S3 bucket passed from the ctrl table for each App")
  logger.info(s"Checking on App Code: ${elem head}")

  listS3Buckets(elem_str(1), elem_str(2)) match {

    case Some(allBktsInfo) =>
      logger.info(s"App: ${elem_str head} provided the bucket name as: ${elem_str(3)}")
      if (allBktsInfo.exists(x => x.getName == elem_str(3))) {
        logger.info(s"Provided S3 bucket: ${elem_str(3)} exists")
        println(s"s3 ${elem_str(3)} bucket exists")
      } else {
        logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
        logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
        excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
        println(s"s3 bucket ${elem_str(3)} doesn't exists")
    }

    case None =>
      logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
      logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
      excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
}

不変性に慣れている場合は、長時間待機してもデータ構造が変更される心配はありません。そのため、自由に論理的に分離されたタスクを、より分離された方法で実行できます。

val (exists, missing) = rows partition bucketExists
missing foreach {row =>
  logger.info(s"WARNING: Provided S3 bucket ${row("s3_primary_bkt_name")} doesn't exist")
  logger.info(s"WARNING: Dropping the App: ${row("app")} from backup schedule")
}
4
Karl Bielefeldt

不変オブジェクトを使用する利点は、受信側がそれを調べたときに特定のプロパティを持つオブジェクトへの参照を受け取り、同じプロパティを持つオブジェクトへの参照を他のコードに与える必要がある場合、単純に渡すことができることです他にだれが参照を受け取ったか、またはオブジェクトに対して何をする可能性があるかに関係なく、オブジェクトへの参照に沿って[他に誰もいないためcanオブジェクトに対して行う]、または受信者が調べる可能性があるときオブジェクト[そのプロパティはすべて、いつ検査されるかに関係なく同じになるため].

対照的に、レシーバーがそれを調べたときに特定のプロパティを持つ可変オブジェクトへの参照を誰かに与える必要があるコード(レシーバー自体が変更しないと仮定)は、レシーバー以外は何も変化しないことを知る必要がありますそのプロパティ、または受信者がそのプロパティにいつアクセスするかを知っており、最後に受信者がプロパティを調べるまで、そのプロパティを変更するものは何もないことを知っています。

不変オブジェクトを次の3つのカテゴリに分類されると考えることは、一般的なプログラミング(関数型プログラミングだけでなく)にとって最も役立つと思います。

  1. たとえ参照があっても、何も変更できないオブジェクト。そのようなオブジェクトとそれらへの参照はvaluesとして動作し、自由に共有できます。

  2. それらへの参照を持つコードによって自分自身を変更できるようにするが、その参照が実際に変更するコードに公開されることのないオブジェクト。これらのオブジェクトは値をカプセル化しますが、値を変更したり、変更する可能性のあるコードに公開したりしないと信頼できるコードとのみ共有できます。

  3. 変更されるオブジェクト。これらのオブジェクトはcontainersとして表示され、それらへの参照はidentifiersとして表示されます。

多くの場合、有用なパターンは、オブジェクトにコンテナを作成させ、後で参照を保持しないと信頼できるコードを使用してコンテナにデータを投入し、ユニバースのどこかに存在する唯一の参照がコードを変更しないコードに含まれるようにすることです。いったん入力されるとオブジェクト。コンテナは変更可能なタイプである可能性がありますが、実際には何も変更しないため、不変であるかのように(*)と推論される場合があります。コンテナへのすべての参照が不変のラッパータイプで保持され、その内容は決して変更されない場合、ラッパーへの参照は自由に共有および検査されるため、そのようなラッパーは、その中のデータが不変オブジェクトに保持されているかのように安全に渡されます。どんなときも。

(*)マルチスレッドコードでは、「メモリバリア」を使用して、スレッドがラッパーへの参照を参照できるようになる前に、コンテナに対するすべてのアクションの効果がそのスレッドから見えるようにする必要があります。これは、完全を期すためにここで言及した特別なケースです。

3
supercat

すでに述べたように、変更可能な状態の問題は、基本的に副作用のより大きな問題のサブクラスであり、関数の戻り値の型は、関数が実際に何をするかを正確に記述していません、なぜならこの場合、それは状態変異も行うからです。この問題は、F *( http://www.fstar-lang.org/tutorial/ )などのいくつかの新しい研究言語によって解決されています。この言語は、型システムと同様のEffect Systemを作成します。関数は、型だけでなくその効果も静的に宣言します。このように、関数の呼び出し元は、関数の呼び出し時に状態変更が発生する可能性があることを認識しており、その効果は呼び出し元に伝播されます。

1