キューを使用して関数を分離する/直接呼び出しを避ける?


8

関数型プログラミングの初心者向けの質問の種類:

私はリッチヒッキーの講演の筆記録をいくつか読んでいますが、彼の有名な講演のいくつかでは、関数を相互に呼び出す代わりにキューを使用することをお勧めしています。(例:デザイン、構成、パフォーマンスシンプルで簡単

私はこれを多くの点で完全に理解していません:

  1. 彼はデータをキューに入れ、各機能にそれを使用させることについて話しているのですか?したがって、関数Aが関数Bを呼び出して独自の計算を実行する代わりに、関数Bが出力をキューにスラップし、関数Aがそれを取得するようにしますか?または、代わりに、関数をキューに置き、その後それらをデータに順次適用することについて話しているのです(確かにそうではありません。大規模な変更が含まれるためです。そして、マルチアリティ関数のキューの乗算や、ツリーのようなものですか? )

  2. どうすれば物事が簡単になりますか?私の直感は、キューが一種の状態になるため、この戦略はより複雑になると思います。そして、「他の関数がスキューしてデータをキューの上に置くとどうなるか」を心配する必要があります。

SOの実装に関する質問への1つの答えは、アイデアがさまざまなキューの束を作成していることを示唆しています。したがって、各関数はその出力を独自のキュー(??)に入れます。しかし、それも私を混乱させます。なぜなら、関数を一度実行している場合、その出力を取得し、その名前を(var、atom、big inハッシュテーブルなど)。対照的に、関数が複数回実行されていて、その出力をキューに貼り付けている場合は、再び自分に状態を課しているため、すべてが呼び出される順序を心配する必要があり、ダウンストリーム関数は純粋ではなくなります。等

明らかに私はここでポイントを理解していません。誰かが少し説明できますか?


最初のリンクにはキューへの参照が1つも表示されませんでしたが、投稿が非常に長く、見逃した可能性があることを認めます。それはプログラミングよりも芸術性についての話のようです。
Robert Harvey

キューは、2番目の記事で2回簡潔に説明されていますが、詳しく説明されていません。いずれにせよ、アプリケーションまたはモジュール間の通信にメッセージングキューを使用するという考えは、しばらく前からありました。処理パイプラインまたは状態エンジンを作成していない限り、これを単一のアプリケーションで実行することはほとんどありません。
ロバートハーヴェイ

これは、「時間/順序/フローを分解する」という
見出し

3
完全に答える価値はありませんが、彼はジョブプールとイベント駆動型プログラミングの概念について、あいまいな説明をしているだけです。したがって、関数呼び出しを汎用Jobオブジェクトにパックし、それをキューにプッシュして、そのキューで1つ以上のワーカースレッドを動作させます。次にJob、はJob完了時にさらにsをキューにディスパッチします。その概念では、戻り値はコールバックに置き換えられます。コールスタックがないためデバッグと検証を行うのは悪夢であり、同じ理由で効率的で柔軟です。
Ext3h

ありがとう。多分私の本当の問題は私がメッセージを理解していないことです!(えっと、smalltalkを学ぶ時間ですか?:
Paul Gowder

回答:


6

これは、一般的な推奨事項というよりは、設計演習です。通常、すべての直接関数呼び出しの間にキューを配置することはありません。それはばかげているでしょう。ただし、直接関数呼び出しの間にキューが挿入されるような関数設計しない場合、再利用可能で構成可能なコードを記述したと正当に主張することはできません。それがリッチ・ヒッキーの主張です。

これは、たとえばApache Sparkが成功した主な理由です。ローカルコレクションで直接関数呼び出しを行っているように見えるコードを記述します。フレームワークは、そのコードをクラスターノード間のキュー上のメッセージの受け渡しに変換します。リッチヒッキーの支持者のような、シンプルで構成可能で再利用可能なコーディングスタイルは、それを可能にします。


しかし、それはメソッドバインディングプロセスの変更だけではありませんか?結局のところ、関数呼び出しは単なる関数呼び出しですよね?その後に何が起こるかは、関数の機能によって異なります。したがって、基本的なインフラストラクチャの設計方法よりも、関数呼び出しの方が重要なようです。
ロバートハーヴェイ

1
別の言い方をすると、「キューフレンドリー」にするために、関数呼び出しにどのような変更を加えますか?
ロバートハーヴェイ

5
キューの反対側にあるコードが同じメモリとIOにアクセスできるとは限りません。キューに適した関数には副作用がなく、入力を期待し、不変で簡単にシリアル化できる出力を生成します。これは、多くのコードベースでテストするのはそれほど簡単ではありません。
カールビーレフェルト

3
ああ、それでは「関数型プログラミングに優しい」。リッチ・ヒッキーがそれについて議論しているので、ちょっと意味があります。
Robert Harvey

0

注意すべき点の1つは、関数型プログラミングを使用すると、メディエーターオブジェクトを介して間接的に関数を相互に接続し、引数を調達して関数にフィードし、結果を必要とする受信者に結果を柔軟にルーティングできることです。したがって、Haskellに、次の例のような単純な直接呼び出しコードがあるとします。

myThing :: A -> B -> C
myThing a b = f a (g a b)

まあ、HaskellのApplicativeクラスとその<$>and <*>演算子を使用して、そのコードを次のように機械的に書き換えることができます。

myThing :: Applicative f => f A -> f B -> f C
myThing a b = f <$> a <*> (g <$> a <*> b)

...今でmyThingは、直接fand を呼び出すのでgはなく、タイプのメディエータを介してそれらを接続していfます。したがって、たとえば、キューイングシステムへのインターフェイスを提供するライブラリによって提供されるタイプfが考えStreamられます。その場合、次のタイプになります。

myThing :: Stream A -> Stream B -> Stream C
myThing a b = f <$> a <*> (g <$> a <*> b)

このようなシステムは存在します。実際、Java 8ストリームをこのパラダイムのバージョンとして見ることができます。次のようなコードを取得します。

List<Integer> transactionsIds = 
    transactions.parallelStream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

ここでは、次の関数を使用しています。

  • t -> t.getType() == Transaction.GROCERY
  • comparing(Transaction::getValue).reversed()
  • Transaction::getId
  • toList()

...そして、それらを互いに直接呼び出す代わりに、Streamシステムを使用してそれらの間を仲介します。このコード例は、Transaction::getId関数を直接呼び出すのではなくStream、以前に残ったトランザクションで関数を呼び出していfilterます。は、Stream関数を間接的に結合し、それらの間で値をルーティングする非常に最小限のキューと考えることができます。

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