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


9

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

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

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

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

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

関数型プログラミングも、保守可能なコードを促進する同じ動機がありますが、OOツールとテクニックを使用する必要性を排除するスタイルを使用しています。


1
私が言うと思います@Rubenほとんどの関数型言語は、変更可能な変数を許可しませんが、それは違う例えば可変変数は、異なる種類の持っているそれらを使用するために作る
JKを。

1
最初の段落で不変と可変を混在させた可能性があると思いますか?
jk。

1
@jk。、彼は確かにそうしました。それを修正するために編集されました。
David Arno

6
@Ruben 関数型プログラミングはパラダイムです。そのため、関数型プログラミング言語は必要ありません。また、F#などの一部のfp言語にはこの機能があります。
クリストフ

1
@Rubenは特に私はhaskellhackage.haskell.org/package/base-4.9.1.0/docs/…でMvarsを考えていました異なる言語にはもちろん異なるソリューションやIORefs hackage.haskell.org/package/base-4.11.1.0があります/docs/Data-IORef.htmlもちろん、モナド内から両方を使用します
jk。

回答:


7

オブジェクト指向のアプローチと比較することで、その重要性が最もよく示されると思います

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

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パラダイムよりもはるかに多くの並列処理を利用できます。

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

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


ありがとう!! 非常に役立ちます。したがって、購入の新しい実装は次のようにOrder Purchase() { return new Order(Status = "Purchased") } なり、ステータスは読み取り専用フィールドになります。?繰り返しますが、なぜこの実践は関数プログラミングパラダイムのコンテキストでより適切ですか?あなたが言及した利点はオブジェクト指向プログラミングにも見られますよね?
rahulaga_dev

OOでは、object.Purchase()がオブジェクトを変更することを期待します。あなたはそれを不変にすることができますが、それではなぜ完全な機能的パラダイムに移動しないのですか
Ewan

純粋にオブジェクト指向のc#開発者なので、問題を視覚化する必要があると思います。それで、関数型プログラミングを採用する言語であなたが言っていることは、購入した注文を返す「Purchase()」関数がクラスまたはオブジェクトにアタッチされることを必要としないでしょう?
rahulaga_dev

3
機能的なc#を記述して、オブジェクトを構造体に変更し、それを不変にして、Func <Order、Order> Purchase
Ewan

12

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

尋ねる重要なことは、不変性の利点は何ですか?答えは、複雑さを回避することです。2つの変数があるxとしyます。どちらもの値で始まります1y13秒ごとに2倍になります。それらのそれぞれの価値は、20日間でどのようになりますか?xになります1。それは簡単です。しかし、それはyより複雑なので、うまくいくには努力が必要です。20日間の何時ですか 夏時間を考慮する必要がありますか?y対の複雑さxはそれだけではありません。

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

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


4
「複雑さを軽減する最も簡単な方法の1つは、デフォルトで物事を不変にして、必要なときにのみ変更可能にすることです」:非常に素晴らしく簡潔な要約。
ジョルジオ

2
@DavidArno説明する複雑さにより、コードの推論が難しくなります。また、「コードは書きにくい、読みにくい、デバッグが難しい...」と言ったときにも触れました。私は不変オブジェクトが好きです。なぜなら、私だけでなく、プロジェクト全体を知らずに見ているオブザーバーによってコードが推論しやすくなるからです。
disassemble-number-5

1
@RahulAgarwal、「しかし、なぜこの問題が関数型プログラミングのコンテキストでより顕著になるのか」。そうではありません。FPは不変性を促進し、問題を回避するため、FPでの問題はそれほど顕著ではないので、あなたの質問に混乱していると思います。
David Arno

1
@djechlin、「13秒の例を不変のコードで分析しやすくするにはどうすればよいですか?」それはできませんy。それは要件です。複雑な要件を満たすために、複雑なコードが必要になる場合があります。私が作ろうとしていたポイントは、不必要な複雑さを回避すべきだということです。値の変更は本質的に固定値よりも複雑であるため、不要な複雑さを回避するために、必要な場合にのみ値を変更します。
David Arno

3
可変性はアイデンティティの危機を引き起こします。変数に単一のIDがなくなりました。代わりに、そのアイデンティティは時間に依存します。したがって、象徴的には、単一のxではなく、x_tファミリーができました。その変数を使用するすべてのコードは、時間についても考慮する必要があり、回答で言及されている追加の複雑さを引き起こします。
Alex Vong

8

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

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

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

基本的にそれが悪いのであれば、オブジェクト指向や関数型プログラミングのパラダイムに関係なく悪いのですよね?

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


4

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")
}

3

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

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

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

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

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

  3. 変更されるオブジェクト。これらのオブジェクトは、コンテナと見なされ、識別子として参照されます

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

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


印象的な答えをありがとう!おそらく私の混乱の原因は、c#のバックグラウンドであり、「c#で関数型のコードを書く」ことを学習しているためだと思います。強制が正しい場合)不変性。
rahulaga_dev

@RahulAgarwal:オブジェクトへの参照に、同じオブジェクトへの他の参照の存在によって影響を受けないをカプセル化したり、同じオブジェクトへの他の参照とそれらを関連付けたり、どちらにも関連付けないIDを持たせたりすることができます。実際の単語の状態が変化した場合、その状態に関連付けられているオブジェクトの値またはIDのいずれかを一定にすることはできますが、両方にすることはできません。一方を変更する必要があります。50,000ドルは、何をすべきかです。
スーパーキャット2018

1

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.