Bacon.jsのFRP(機能的なリアクティブプログラミング)

多くの場合、かなり複雑なJavaScriptアプリケーションを作成するときに、アプリケーションが本来どおりに機能しなくなった理由、またはその逆が突然機能した理由が完全に理解不能になるときがきます。 アプリケーション要素間には非常に多くの接続があるため、優れたデバッガーを使用してもそれらを追跡することは非常に困難です。 ここにジレンマがあります。一方で、よく知られたJSアプリケーションの作成手法がありますが、これは欠陥に気付かないほど深く説明されています。 一方で、新しいものを試すために反対側行くことを勧めるライブラリがたくさんあります。 これらのライブラリには、JavaScript FRP実装を提供するBacon.jsが含まれています。



FRPとその応用感覚についてのいくつかの言葉。

関数型プログラミングのタオに入らない場合は、Web開発にとって特に魅力的ないくつかのFRPポイントを強調できます。 これは:



もちろん、州では、すべてがそれほど透明ではありません。 最終的には、JavaScriptに固有の問題があります。 したがって、ブラウザの内部のどこかで、すべて同じように、bacon.jsを使用しないコードで発生するのと同じことが起こりますが、全体としては、これはもはや開発者の関心事ではないということです。 開発者のタスクは、アプリケーションのロジックについて考えることです。



FRPは、データソースがイベントストリームであると想定しています。 データ自体は特定の時点でのストリームの状態であるため、データを変更すると、前者に依存する他のデータが即座に変更されます。 その結果、一部のデータが他のデータに依存することを説明する構造ツリーが作成され、システムが透過的になり、簡単に消化できるようになります。



ここで、アプリケーションパートに進みます。つまり、例を使用して、JSでの命令型プログラミングに対するFRP全般および特にBacon.jsの利点を示します。 例として、よく知られている倉庫番ゲームを取り上げます。これは、Bacon.jsの有無にかかわらず、違いを示すために書きました。



あまり詳細に入らないように、すぐに結果を見ることができます。 アクションのロジックを担当するゲームの機能部分に焦点を当てます。 誰かが原材料を知りたがっていたくなるような欲求を持っているなら、 どうぞ 。 一言で言えば、すべてが次のように動作します:セル(DHTML)でフィールドの幅と高さを設定し、レベル(JSONオブジェクト)を描画し、矢印を使用してフィールド全体でプレーヤーを移動し(背景が緑色のブロック)、すべてのときに黄色のセルを青色に移動しようとします黄色のセルは青になっています-レベルは合格です。



実用化

そして今、最も重要なことです。 通常、ユーザーのアクションに応じてブラウザーが生成するイベントをリッスンして、次に何をすべきかを見つけます。



$(document).on("keydown", function(e){ //     });
      
      





ゲームは4つのキーで制御されます。 そして、構文的にはこのように見える条件チェックの問題がすぐに発生します。



 if(e.keyCode >= 37 && e.keyCode <= 40){ //  ,    }
      
      







本当に何も始める時間はありませんでしたが、すでにこのような状況で仕事をしなければなりません。 たとえば、移動の方向を計算します。 ゲームの論理構造をさらに深く掘り下げると、現在の暗黙的な状態を判断することのみを目的とする多数の環境チェックに直面しています。



bacon.jsは何を提供しますか? bacon.jsでは、ストリーム(EventStream)とプロパティ(Property)が定義されます-特定の時間でのストリームの状態。 ストリームは処理、結合、結合できます。 メソッドの視覚的な図があります。 したがって、必要な反応の説明は、ストリーム内のイベントの順序とデータ変換の説明に縮小されます。 アイデアは、単一のイベントを追跡してそれぞれを個別に処理するのではなく、データソース、つまり必要なイベントのみを抽出したり変換したりできるイベントストリームを取得することです。 たとえば、フィルターの使用:



 var keyDowns = $(document).asEventStream("keydown"); //  keydown  $(document) var arrowDowns = keyDowns.filter(isArrows); // , ,    isArrows function isArrows(e){ // asEventStream     jQuery.Event return e.keyCode >= 37 && e.keyCode <= 40 }
      
      





またはマップを使用する:



 var changeDirection = $(document).asEventStream("keydown") .filter(isArrows) // filter  true,    ,    .map(selectDirection) //map   selectDirection(event) .onValue(function(x){ //   ,    //     }); function selectDirection(e){ return { x : e.keyCode % 2 ? e.keyCode - 38 : 0, y : !(e.keyCode % 2) ? e.keyCode - 39 : 0 } }
      
      





または何か他のもの。 ストリームとプロパティを操作する方法は多数あります。 重要なのは、Bacon.jsが提供する機能でさえありませんが、これらのイベントストリームで後でできることです。 bacon.jsを使用しないゲームの一般的なアルゴリズムが条件と状態の洗練された迷路である場合、FRPを使用して、プログラムの状態とデータソースの宣言的な記述を実現します。

キーボード入力やオブジェクトのクリックなど、必要なイベントが発生したときにシステムの状態を変更するのに慣れています。 必要なイベントを理解するには、ハンドラーをハングさせ、イベントの発生を注意深く監視する必要があります。 すべての状態に独自のアクションを持たせるために、多くの計画を立て、ユーザーの一般的な動作を予測する必要があります。 しかし、実際には、開発者として、システムの状態に論理的に影響しない限り、ユーザーが何をしたかにはあまり興味がありません。 このアプローチでは、「真実はユーザーが他のボタンではなく、この特定のボタンをキーボードに突っ込んだという」スタイルのチェックを必要としないため、イベントの宣言的な記述は人生を大いに促進します。



 var playerMove = $(document).asEventStream("keydown") // keydown  $(document) .filter(isArrows) //     .map(player) //  .map(nextCell); //,   . // , ,        var playerNextEmpty = playerMove.filter(isEmpty).onValue(function(nov){ //  . }); // , ,       -   var goalMove = playerMove.map(isGoal).filter(function(x){return x}); // , ,     ,     -  var goalNextEmpty = goalMove.map(nextCell).filter(isEmpty).onValue(function(x){ //   });
      
      





ご覧のとおり、構文自体が明確な記述を推奨しています。 実際、scan()メソッドと結合()メソッドを使用することにより、すべてをさらにシンプルにすることができますが、最も単純なmap()およびfilter()メソッドを表示したかったのです。



bacon.jsなしで機能全体を提供するわけではありませんが、キーダウンイベント中にゲームの仕組みを処理するコールバックはこれで終わります。





宣言的アプローチは、新しい機能を備えた新しい要素でシステムを補完する必要がある場合にタスクを簡素化します。 たとえば、地雷をフィールドに追加します。 プレイヤーが地雷に着地した場合、またはプレイヤーに対してブロックを押した場合、ゲームは失われたと見なされます。



 // , ,   ,    -  var playerNextMine = playerMove.filter(isMine); // , ,   ,     -  var goalNextMine = goalMove.map(nextCell).filter(isMine); //   playerNextMine  goalNextMine. var mineAlert = goalNextMine.merge(playerNextMine).onValue(function(x){ //  });
      
      







命令型スタイルでまったく同じ機能を追加することは、はるかに困難です。 状態の条件付きチェックと識別の複雑なシステムでは、別の可能なシナリオを組み込む必要があります。 他のシナリオとこのシナリオをどのように組み合わせるか、競合が発生するかどうかなどについて考える。



長所と短所









これはすべて理にかなっていますか?

上記のすべてから、結論を出すことができます。 アプリケーションの複雑さに対してbacon.js(および一般的にFRP)を使用する必要があることを示す仮想曲線があります。 bacon.jsを使用して美をサイトに誘導したり、サインフォームだけに使用したりする意味がないことは明らかです。 ライブラリは重いものではありませんが、すぐに使用できる場所と必要のない場所を確認する方が良いでしょう。 Bacon.jsは、宣言型プログラミングなしではナビゲートが困難な場合に使用するのが賢明です。 システムが大きく、膨大な数の要素が含まれている場合でも、これらの要素が互いに依存している場合にのみベーコンを使用するのが理にかなっています。1つのデータを変更すると、他のデータも変更される必要があります。 この意味での複雑さは、アプリケーションのサイズやアルゴリズムの全体的な複雑さではなく、アプリケーションの論理構造のサイズを意味します。



代替案

JSライブラリーには、Microsoft RxJSがあります。 エルムのようなものがあります。 ClojureScriptのような興味深いものがあります。 この問題の調査に少し時間を割いて、便利なオプションを選択できます。



材料

GitHub: Bacon.jsウェブサイト

いくつかの良いFRPのもの: onetwothree



All Articles