関数型スタイルでプログラミングする場合、アプリケーションロジックを通して織り込む単一のアプリケーション状態がありますか?


12

どのように私は、次のすべてを持っているシステムを構築します

  1. 不変オブジェクトで純粋な関数を使用します。
  2. 関数が必要とする関数データのみに渡し、それ以上(つまり、大きなアプリケーション状態オブジェクトはなし)
  3. 関数への引数が多すぎることは避けてください。
  4. 関数に渡されるパラメーターが多すぎるのを避けるために、関数にパラメーターをパックおよびアンパックするためだけに新しいオブジェクトを作成する必要はありません。複数のアイテムを単一のオブジェクトとして関数にパックする場合、一時的に構築されたものではなく、そのオブジェクトをそのデータの所有者にしたい

Stateモナドはルール2に違反しているように思えますが、モナドを介して織り込まれているため明らかではありません。

どういうわけかレンズを使用する必要があると感じていますが、非機能言語についてはほとんど書かれていません。

バックグラウンド

演習として、既存のアプリケーションの1つをオブジェクト指向スタイルから機能スタイルに変換しています。私が最初にやろうとしているのは、アプリケーションの内部コアをできるだけ多く作成することです。

私が聞いたことの1つは、純粋に機能的な言語で「状態」を管理する方法であり、これは私がステートモナドによって行われていると信じていることです。 「そのままの世界」を選択し、関数が戻ると、変化した世界の状態を返します。

たとえば、純粋に機能的な方法で「hello world」を実行する方法は、プログラムに画面の状態を渡し、「hello world」が印刷された画面の状態を受け取るようなものです。技術的には、純粋な関数を呼び出しているので、副作用はありません。

それに基づいて、アプリケーションを調べました。1。最初に、すべてのアプリケーション状態を単一のグローバルオブジェクト(GameState)に入れます。2。次に、GameStateを不変にしました。変更することはできません。変更が必要な場合は、新しいものを作成する必要があります。これを行うために、コピーコンストラクターを追加しました。オプションで、変更された1つ以上のフィールドを使用します。3.各アプリケーションに、GameStateをパラメーターとして渡します。関数内で、予定通りの処理を行った後、新しいGameStateを作成して返します。

純粋に機能的なコアと、そのGameStateをアプリケーションのメインワークフローループにフィードする外側のループの仕組み。

私の質問:

さて、私の問題は、GameStateには約15種類の不変オブジェクトがあることです。最下位レベルの関数の多くは、スコアの保持など、これらのオブジェクトの一部のみで動作します。それで、スコアを計算する関数があるとしましょう。今日、GameStateはこの関数に渡され、新しいスコアで新しいGameStateを作成してスコアを変更します。

それについて何かが間違っているようです。この関数はGameState全体を必要としません。スコアオブジェクトが必要です。そこで、スコアを渡すように更新し、スコアのみを返します。

それは理にかなっているように思えたので、私は他の機能をさらに使いました。一部の関数では、GameStateから2、3、または4つのパラメーターを渡す必要がありますが、アプリケーションの外側のコア全体でパターンを使用しているため、ますます多くのアプリケーション状態を渡しています。たとえば、ワークフローループの先頭で、メソッドを呼び出し、メソッドを呼び出すメソッドなどを呼び出して、スコアが計算されるまで続けます。つまり、一番下の関数がスコアを計算するという理由だけで、現在のスコアがこれらすべてのレイヤーに渡されます。

だから今私は時々数十のパラメータを持つ関数を持っています。これらのパラメーターをオブジェクトに入れてパラメーターの数を減らすこともできますが、そのクラスは、単に渡すことを避けるために呼び出し時に単純に構築されるオブジェクトではなく、状態アプリケーション状態のマスターの場所にしたいです複数のパラメータで、それらを解凍します。

だから今、私が持っている問題は私の関数があまりにも深くネストされていることだろうかと思っています。これは小さな関数が必要なためです。そのため、関数が大きくなったらリファクタリングし、複数の小さな関数に分割します。しかし、そうすることでより深い階層が生成され、外部関数が直接それらのオブジェクトを操作していない場合でも、内部関数に渡されたものはすべて外部関数に渡される必要があります。

この問題を回避する方法に沿って単にGameStateを渡すように見えました。しかし、関数が必要とする以上の情報を関数に渡すという元の問題に戻りました。


1
私はデザインの専門家ではなく、特に機能的ですが、あなたのゲームは本質的に進化する状態を持っているので、関数型プログラミングはアプリケーションのすべての層に適合するパラダイムであると確信していますか?
ウォルフラット

ウォルフラット、関数型プログラミングの専門家と話をすると、関数型プログラミングのパラダイムは進化する状態を管理するためのソリューションを持っていると言うでしょう。
大社リン

あなたの質問は、私だけが述べているように広く見えました。ここでの状態の管理のみが開始点である場合は、stackoverflow.com
questions / 1020653 /…

2
@DaishaLynn質問を削除する必要はないと思います。それは支持されており、誰もそれを閉じようとはしていないので、私はそれがこのサイトの範囲外であるとは思わない。これまでのところ答えがなかったのは、それが比較的ニッチな専門知識を必要としているからかもしれません。しかし、だからと言って、最終的には見つけられず、答えられないというわけではありません。
ベンアーロンソン

2
実質的な言語支援なしで、複雑で純粋な機能プログラムで可変状態を管理することは大きな苦痛です。Haskellでは、モナド、簡潔な構文、非常に優れた型推論のために管理できますが、依然として非常に迷惑です。C#では、かなり多くの問題があると思います。
モニカの

回答:


2

良い解決策があるかどうかはわかりません。これは答えかもしれませんが、コメントするには長すぎます。私は同様のことをしていて、次のトリックが役立ちました:

  • GameState階層的に分割して、15個ではなく3〜5個の小さなパーツを取得します。
  • インターフェースを実装して、メソッドに必要な部分のみが見えるようにします。あなたが本当のタイプについて自分に嘘をつくので、決してそれらをキャストしないでください。
  • また、パーツにインターフェースを実装して、渡すものを細かく制御できるようにします。
  • パラメーターオブジェクトを使用しますが、控えめに実行し、それらを独自の動作を持つ実際のオブジェクトに変換してください。
  • 長いパラメーターリストよりも、必要以上に渡す方が良い場合があります。

だから今、私が持っている問題は私の関数があまりにも深くネストされていることだろうかと思っています。

そうは思いません。小さな関数にリファクタリングするのは正しいことですが、多分あなたはそれらをより良く再編成できるでしょう。不可能な場合もあれば、問題を2回目(または3回目)で確認する必要がある場合もあります。

設計を変更可能な設計と比較します。書き換えによって悪化したものはありますか?もしそうなら、あなたが最初にやったのと同じ方法でそれらを良くすることはできませんか?


カレーを使用できるように関数が1つのパラメーターのみを取るように設計を変更するように誰かに言われました。その1つの関数を試したので、DeleteEntity(a、b、c)を呼び出す代わりに、DeleteEntity(a)(b)(c)を呼び出します。それはかわいいです、そしてそれは物事をより構成可能にするはずですが、私はまだそれを手に入れていません。
大社リン

@DaishaLynn私はJavaを使用していますが、カリー化のための甘い構文糖はないので、(私にとって)試す価値はありません。私たちの場合、高階関数の可能な使用法についてはかなり懐疑的ですが、それがあなたのために働いたかどうか教えてください。
maaartinus

2

私はC#と話すことはできませんが、Haskellでは、状態全体を渡すことになります。これは明示的に、またはStateモナドで行うことができます。関数が必要以上の情報を受け取るという問題に対処するためにできることの1つは、Has型クラスを使用することです。(慣れていない場合、Haskellの型クラスはC#インターフェイスに少し似ています。)状態の各要素Eに対して、Eの値を返す関数getEを必要とする型クラスHasEを定義できます。これらすべてのタイプクラスのインスタンスを作成しました。実際の関数では、Stateモナドを明示的に要求する代わりに、必要な要素のHasタイプクラスに属するモナドが必要です。それは関数が使用しているモナドで何ができるかを制限します。このアプローチの詳細については、Michael SnoymanのReaderTデザインパターンに投稿してください

渡される状態をどのように定義しているかに応じて、おそらくC#でこのようなものを複製できます。次のようなものがある場合

public class MyState
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
}

インターフェイスIHasMyIntIHasMyStringメソッドGetMyIntGetMyStringそれぞれ定義できます。状態クラスは次のようになります。

public class MyState : IHasMyInt, IHasMyString
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
    public double MyDouble {get; set; }

    public int GetMyInt () 
    {
        return MyInt;
    }

    public string GetMyString ()
    {
        return MyString;
    }

    public double GetMyDouble ()
    {
        return MyDouble;
    }
}

メソッドには、必要に応じてIHasMyInt、IHasMyString、またはMyState全体が必要になる場合があります。

その後、関数定義でwhere制約を使用して、状態オブジェクトを渡すことができますが、doubleではなくstringとintにしか到達できません。

public static T DoSomething<T>(T state) where T : IHasMyString, IHasMyInt
{
    var s = state.GetMyString();
    var i = state.GetMyInt();
    return state;
}

それは面白い。したがって、現在、関数を呼び出して値で10個のパラメータを渡し、「gameSt'ate」を10回渡しますが、「IHasGameScore」、「IHasGameBoard」などの10種類のパラメータタイプに渡します。関数がその1つの型のすべてのインターフェイスを実装する必要があることを示すことができる単一のパラメーターを渡す方法でした。「一般的な制約」でそれができるかどうか疑問に思います。それを試してみましょう。
大社リン

1
動いた。ここでは動作しています:dotnetfiddle.net/cfmDbs
大社リン

1

ReduxまたはElmについて、またこの質問にどのように対処するかについて学ぶのは良いと思います。

基本的に、状態全体とユーザーが実行したアクションを取得して新しい状態を返す1つの純粋な関数があります。

その後、その関数は他の純粋な関数を呼び出し、それぞれが特定の状態を処理します。アクションによっては、これらの関数の多くは何もせずに元の状態を変更せずに返すことがあります。

詳細については、Google the Elm ArchitectureまたはRedux.js.orgをご覧ください。


エルムは知りませんが、Reduxに似ていると思います。Reduxでは、すべてのリデューサーが状態の変更ごとに呼び出されませんか?非常に非効率的に聞こえます。
大社リン

低レベルの最適化に関しては、測定しないでください。実際には、十分に高速です。
ダニエルT.

ダニエルに感謝しますが、私にはうまくいきません。コントロールがコントロールを気にするかどうかに関係なく、UIのすべてのコンポーネントにデータが変更されるたびに通知しないように、十分な開発を行いました。
大社リン

-2

あなたがしようとしているのは、オブジェクト指向言語を純粋に機能的なものであるかのように、使用すべきではない方法で使用することだと思います。オブジェクト指向言語がすべて悪であったというわけではありません。どちらのアプローチにも利点があるため、オブジェクト指向スタイルと機能スタイルを混在させ、一部のコードを機能的にする一方で、オブジェクト指向のままで他のすべての再利用性、継承、政治哲学を活用できるようになりました。幸いなことに、私たちはもはやどちらのアプローチにも縛られていないのに、なぜあなたはそれらの1つに自分を閉じ込めようとしているのですか?

あなたの質問に答えます:いいえ、アプリケーションロジックを通じて特定の状態を織り込むことはしませんが、現在のユースケースに適したものを使用し、利用可能な技術を最も適切な方法で適用します。

C#は、(まだ)望みどおりに機能するように使用する準備ができていません。


3
この答えにも口調にも感動しません。私は何も乱用していません。より機能的な言語として使用するために、C#の限界に挑戦しています。珍しいことではありません。あなたはそれに対して哲学的に反対しているようですが、それは問題ありませんが、その場合はこの質問を見てはいけません。あなたのコメントは誰にも役に立たない。進め。
大社リン

@DaishaLynn、あなたは間違っています、私はそれに反対していません、そして実際、私はそれをたくさん使います...しかしそれが自然で可能であり、オブジェクト指向言語を機能的な言語に変えようとしないのは、それがヒップだからですそうするには。私の答えに同意する必要はありませんが、ツールを適切に使用していないという事実は変わりません。
t3chb0t

そうするのはヒップだからです。C#自体は、機能的なスタイルの使用に向かっています。Anders Hejlsberg自身もそのように指摘しています。私はあなたが言語の主流の使用にのみ興味があることを理解しており、それが適切である理由と時期を理解しています。なぜあなたのような人がこのスレッドにいるのか、私にはわかりません。
大社リン

@DaishaLynn質問やアプローチを批判する回答に対処できない場合は、おそらくここで質問をするべきではありません。次回は、100%自分のアイデアをサポートする回答にのみ興味があるという免責事項を追加する必要があります。真実を聞きたいが、むしろ支持的な意見を得たい。
t3chb0t

お互いにもう少し心を込めてください。言語を軽disすることなく批評をすることは可能です。機能的なスタイルでC#をプログラミングしようとすることは、確かに「乱用」や端的なケースではありません。これは、他の言語から学ぶために多くのC#開発者が使用する一般的な手法です。
zumalifeguard
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.