6547


数値入力イベントのstreamと  
`-1-2-3---4-5-6---7-8-9--`

演算子入力イベントのstreamから、  
`\-------+-------+--------`

計算に用いるオペランドのstreamを生成して、  
`\---123-----456-----789--`

最終的に演算結果のstreamを生成する  
`\---123-----579-----1368-`

という流れかなと考えているんですが、実装方法がイマイチつかめていません。

試行錯誤をしてみた結果が以下ですが、結局scanで無理やり実装している感じです。

[code]

    var ds = $('.digit').asEventStream('click').map(function(d) {
      return parseInt(d.target.textContent)
    });
    var os = $('.operation').asEventStream("click").map('.target.textContent');
    
    var buffer = ds.merge(os).scan({
      current: 0,
      result: 0,
      display: 0,
      nextOperation: '+'
    }, function(buf, v) {
      if (isFinite(v)) {
        buf.current = buf.current * 10 + v;
        buf.display = buf.current;
      } else {
        buf.result = eval('buf.result ' + buf.nextOperation + ' buf.current');
        buf.result = isFinite(buf.result) ? buf.result : 0;
        buf.display = buf.result;
        buf.current = 0;
        buf.nextOperation = v;
      }
      return buf;
    });
    
    buffer.onValue(console, 'log');
    buffer.map('.display').onValue($('#display'), 'text');
[/code]

[code]

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <div>
      <button class="digit">1</button>
      <button class="digit">2</button>
      <button class="digit">3</button>
      <button class="digit">4</button>
      <button class="digit">5</button>
      <button class="digit">6</button>
      <button class="digit">7</button>
      <button class="digit">8</button>
      <button class="digit">9</button>
      <button class="digit">0</button>
      <button class="operation">+</button>
      <button class="operation">-</button>
      <button class="operation">*</button>
      <button class="operation">/</button>
    </div>
    <label id="display"></label>
[/code]

FRP的には本来どのように実装するのでしょうか？


CC BY-SA 4.0
2015-02-15T14:17:25.160
6531
2019-12-13T18:28:25.803
2019-12-13T18:25:28.820
32986
8298
question
javascript jquery reactive-programming
電卓アプリをFRP的に実装するには？


Bacon.updateを使うのは一つの手だと思います。

以下は、あげていただいたコードをBacon.updateを用いて書きなおしてみたものです。

[code]

    var ds = $('.digit').asEventStream('click').map(function(d) {
      return parseInt(d.target.textContent)
    });
    
    var os = $('.operation').asEventStream("click").map('.target.textContent');
    
    var prog = Bacon.update({
      buf1: 0,
      buf2: 0,
      result: '',
      os: ''
    }, [ds], function(o, ds) {
      var result = o.buf1 * 10 + ds;
      return {
        buf1: result,
        buf2: o.buf2,
        result: result,
        os: o.os
      };
    }, [os], function(o, os) {
      var result;
      switch (o.os) {
        case '+':
          result = (o.buf2 + o.buf1);
          break;
        case '-':
          result = (o.buf2 - o.buf1);
          break;
        case '*':
          result = (o.buf2 * o.buf1);
          break;
        case '/':
          result = (o.buf2 / o.buf1);
          break;
        default:
          result = o.buf1;
      }
      return {
        buf1: 0,
        buf2: result,
        result: result,
        os: os
      };
    });
    var output = prog.map('.result').onValue($('#display'), 'text');
[/code]

[code]

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bacon.js/0.7.47/Bacon.js"></script>
    <div>
      <button class="digit">1</button>
      <button class="digit">2</button>
      <button class="digit">3</button>
      <button class="digit">4</button>
      <button class="digit">5</button>
      <button class="digit">6</button>
      <button class="digit">7</button>
      <button class="digit">8</button>
      <button class="digit">9</button>
      <button class="digit">0</button>
      <button class="operation">+</button>
      <button class="operation">-</button>
      <button class="operation">*</button>
      <button class="operation">/</button>
    </div>
    <label id="display"></label>
[/code]

コードをちょっと変えただけなので、もっと良くする余地は残っていると思いますが。

stream.mergeを使うと、せっかくEventStreamやPropertyを重ねあわせたのにもかかわらず、  
あとからイベントの種類ごとに処理を分岐しなければいけませんが、  
Bacon.updateを使うと、一つのオブジェクトに対して、複数のイベントとそれに対応した処理を分けて書けるので便利です。

Bacon.jsのブログでも、ショッピングカートの例としてBacon.updateが用いられています。  
<http://baconjs.blogspot.jp/2014/12/structuring-real-life-applications.html>  
<http://baconjs.blogspot.jp/2014/12/bus-of-doom.html>


CC BY-SA 4.0
2015-02-16T04:27:13.460
6546
2019-12-13T18:28:25.803
2019-12-13T18:28:25.803
32986
8305
6531
answer


FRP ということではなく、 [Bacon.js](http://www.cheatography.com/proloser/cheat-
sheets/bacon-js/) 限定ですが、ストリームの出力が異なるので、 `Bacon.update` でまとめるとすっきりします。
`sampledBy` が使えるかと思いましたが、どこに蓄積される情報を置くかが難しいですね。

[code]

    function newData(result, curr, op) {
      return { result: result, curr: curr, op: op };
    }
    
    function accumNum(orig_num, num) {
      return orig_num * 10 + num;
    }
    
    // .digit -> number
    var digit = $('.digit').asEventStream('click')
      .map(function (ev) { return parseInt(ev.target.textContent); });
    
    // .operation -> function(a, b)
    var operation = $('.operation').asEventStream("click")
      .map(function(e) {
        return function(a, b) {
          return b != null ? eval('a' + e.target.textContent + "b") : a;
        };
      });
    
    // .reset -> tick
    var reset = $('.reset').asEventStream("click");
    
    Bacon.update(newData(0, null, accumNum), 
      [digit], function(data, value) {
        return newData(data.result, accumNum(data.curr, value), data.op);
      }, [operation], function(data, f_ab) {
        return newData(data.op(data.result, data.curr), null, f_ab);
      }, [reset], function(data, unused) {
        return newData(0, null, accumNum);
      }
    ).onValue(function(v) {
      console.log(v); $('#display').text(v.result);
    });
[/code]

[code]

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bacon.js/0.7.47/Bacon.min.js"></script>
    
    Calculator (bacon.js) <hr/>
    <div>
        <button class="digit">1</button>
        <button class="digit">2</button>
        <button class="digit">3</button>
        <button class="digit">4</button>
        <button class="digit">5</button>
        <button class="digit">6</button>
        <button class="digit">7</button>
        <button class="digit">8</button>
        <button class="digit">9</button>
        <button class="digit">0</button>
    </div><hr/> 
    <div>
        <button class="operation">+</button>
        <button class="operation">-</button>
        <button class="operation">*</button>
        <button class="operation">/</button>
        <button class="reset">CLR</button>
    </div><hr/>
    <label id="display"></label>
[/code]


CC BY-SA 3.0
2015-02-16T04:32:23.100
6547
2015-02-16T04:44:59.697
2015-02-16T04:44:59.697
4978
4978
6531
answer