Functional Reactive Programming 実践編
∼ 画面作成、リクエスト処理 ∼
@rf0444
利用ライブラリ
• Bacon.js
• https://github.com/raimohanska/bacon.js
• jQuery
おしながき
• EventStream と Property
• 画面を作る
• リクエスト処理
EventStream と Property
EventStream
• 発生するイベントの列 を表す
• クリックされた、など
時間
値
Property
• 時間によって変化する値 を表す
• View に表示する値、最後に返ってきたレスポンス など
時間
値
EventStream / Property
• EventStream#merge(EventStream)
• 2つの EventStream をくっつけた
EventStream を作る。
時間
値
時間
値
時間
値
e1
e2
e1....
EventStream / Property
• EventStream#toProperty([initVal])
• EventStream に 値が流れてくる
タイミングで、値が変化する
Property を作る。
• 引数に初期値を指定...
EventStream / Property
• Property#changes()
• Property の値が変化した
タイミングで、変化後の
値が流れる EventStream
を作る。
• 初期値は流れない
時間
値 p.change...
EventStream / Property
• Property#sampledBy(EventStream)
• EventStream に値が流れた時点
の Property の値が流れる
EventStream を作る。
時間
値
時間...
画面を作る
設計方針
• 出来るだけ副作用を排除したい。
• Callback 内処理を、単純な副作用だけにしたい。
例: Click Counter
クリックすると
増える
例: Click Counter
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = functi...
例: Click Counter
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = functi...
例: Click Counter
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = functi...
例: Click Counter
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = functi...
例: Click Counter
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = functi...
例: Counting Button
クリックすると増える
例: Counting Button
クリックすると増える
Property を作るために、
作成後の button の EventStream が必要
例: Counting Button
クリックすると増える
Property を作るために、
作成後の button の EventStream が必要
EventStream -> Property な
関数を渡すようにしてみる
例: Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = func...
例: Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = func...
例: Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = func...
例: Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = func...
例: Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = func...
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton = function(conf) {
	 	 va...
例: Cross Counting Button
クリックすると 反対側が増える
例: Cross Counting Button
クリックすると 反対側が増える
Property を作るために、
別の button の EventStream が必要
(相互に要求)
例: Cross Counting Button
クリックすると 反対側が増える
Property を作るために、
別の button の EventStream が必要
(相互に要求)
Bus を使い、
一旦 EventStream として渡...
例: Cross Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton ...
例: Cross Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton ...
例: Cross Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton ...
例: Cross Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton ...
例: Cross Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton ...
例: Cross Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton ...
例: Cross Counting Button
$(function() {
	 var constant = function(x) { return function() { return x; }; };
	 var mkButton ...
リクエスト処理
Bacon.fromPromise
• jQuery の ajax 系メソッドが返
す Promise オブジェクトから、
EventStream を作る。
Bacon.fromPromise
• jQuery の ajax 系メソッドが返
す Promise オブジェクトから、
EventStream を作る。
assign は、イベント登録
を解除する関数を返す
流れてきたレスポンス
• レスポンスがエラーの場合、そのままでは assign 等に流れていかない。
エラーレスポンスの処理
エラーレスポンスの処理
• レスポンスがエラーの場合、そのままでは assign 等に流れていかない。
• mapError メソッドを使って、エラー系の流れを変換関数を通して本流へ流
す。
流れてきたエラーレスポンス
エラー系の流れをそのまま...
EventStream#flatMap/flatMapLatest
• EventStream が流れてくる EventStream を、中の EventStream に流れる値が
流れてくる EventStream にする
引用: https:/...
EventStream#flatMap/flatMapLatest
• EventStream が流れてくる EventStream を、中の EventStream に流れる値が
流れてくる EventStream にする
引用: https:/...
リクエスト処理の例
右に入力したパスに
GET リクエストを飛ばす
返ってきたレスポンスを表示
ヘルパ
var constant = function(x) { return function() { return x; }; };
var id = function(x) { return x; };
var left = functi...
ヘルパ
var constant = function(x) { return function() { return x; }; };
var id = function(x) { return x; };
var left = functi...
ヘルパ
var constant = function(x) { return function() { return x; }; };
var id = function(x) { return x; };
var left = functi...
ヘルパ
var constant = function(x) { return function() { return x; }; };
var id = function(x) { return x; };
var left = functi...
実装
var input = $('<input type="text" />').width(400);
var bs = mkButton.streams();
var request = bs.clicked.map(function()...
実装
var input = $('<input type="text" />').width(400);
var bs = mkButton.streams();
var request = bs.clicked.map(function()...
実装
var input = $('<input type="text" />').width(400);
var bs = mkButton.streams();
var request = bs.clicked.map(function()...
実装
var input = $('<input type="text" />').width(400);
var bs = mkButton.streams();
var request = bs.clicked.map(function()...
実装
var input = $('<input type="text" />').width(400);
var bs = mkButton.streams();
var request = bs.clicked.map(function()...
実装
var input = $('<input type="text" />').width(400);
var bs = mkButton.streams();
var request = bs.clicked.map(function()...
実装
var input = $('<input type="text" />').width(400);
var bs = mkButton.streams();
var request = bs.clicked.map(function()...
設計
• 実際には、streams から properties を作る部分や、リクエストを飛ばす部分
は、別モジュールにしておくといい。
ViewLogic
Storage
Ajax
App
get streams,
create from p...
Upcoming SlideShare
Loading in …5
×

FRP in Practice

1,235 views

Published on

  • Be the first to comment

FRP in Practice

  1. 1. Functional Reactive Programming 実践編 ∼ 画面作成、リクエスト処理 ∼ @rf0444
  2. 2. 利用ライブラリ • Bacon.js • https://github.com/raimohanska/bacon.js • jQuery
  3. 3. おしながき • EventStream と Property • 画面を作る • リクエスト処理
  4. 4. EventStream と Property
  5. 5. EventStream • 発生するイベントの列 を表す • クリックされた、など 時間 値
  6. 6. Property • 時間によって変化する値 を表す • View に表示する値、最後に返ってきたレスポンス など 時間 値
  7. 7. EventStream / Property • EventStream#merge(EventStream) • 2つの EventStream をくっつけた EventStream を作る。 時間 値 時間 値 時間 値 e1 e2 e1.merge(e2)
  8. 8. EventStream / Property • EventStream#toProperty([initVal]) • EventStream に 値が流れてくる タイミングで、値が変化する Property を作る。 • 引数に初期値を指定できる。 (なしも可) 時間 値 時間 値 v0 es es.toProperty(v0)
  9. 9. EventStream / Property • Property#changes() • Property の値が変化した タイミングで、変化後の 値が流れる EventStream を作る。 • 初期値は流れない 時間 値 p.changes() 時間 値 p
  10. 10. EventStream / Property • Property#sampledBy(EventStream) • EventStream に値が流れた時点 の Property の値が流れる EventStream を作る。 時間 値 時間 値 時間 値 p es p.sampledBy(es)
  11. 11. 画面を作る
  12. 12. 設計方針 • 出来るだけ副作用を排除したい。 • Callback 内処理を、単純な副作用だけにしたい。
  13. 13. 例: Click Counter クリックすると 増える
  14. 14. 例: Click Counter $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el); });
  15. 15. 例: Click Counter $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el); }); ボタン右のテキストの値 (時間によって変化する)
  16. 16. 例: Click Counter $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el); }); クリックされたら 1 が 流れてくる EventStream
  17. 17. 例: Click Counter $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el); }); クリックされたら 1 が 流れてくる EventStream 0 から順に、足して畳み込んでいく (初期値 0 の Property ができる)
  18. 18. 例: Click Counter $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />').text(conf.text); return { el: el, streams: { clicked: el.asEventStream('click') } }; }; var mkText = function(conf) { var el = $('<span />'); conf.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ text: 'click' }); var text = mkText({ text: button.streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }); $('body').append(button.el).append(' ').append(text.el); }); 副作用 (値変化時の処理登録、 テキストの中身を変更) 副作用 (DOM 要素登録)
  19. 19. 例: Counting Button クリックすると増える
  20. 20. 例: Counting Button クリックすると増える Property を作るために、 作成後の button の EventStream が必要
  21. 21. 例: Counting Button クリックすると増える Property を作るために、 作成後の button の EventStream が必要 EventStream -> Property な 関数を渡すようにしてみる
  22. 22. 例: Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el); });
  23. 23. 例: Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el); }); EventStream -> Property な関数
  24. 24. 例: Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el); }); 渡された EventStream を 畳み込んで、Propertyを作る
  25. 25. 例: Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el); }); 先に EventStream を 作っておいて、
  26. 26. 例: Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el); }); 渡された関数に適用 して、Property を得る 先に EventStream を 作っておいて、
  27. 27. $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = function(conf) { var el = $('<button />'); var streams = { clicked: el.asEventStream('click') }; var properties = conf.f(streams); properties.text.assign(function(text) { el.text(text); }); return { el: el }; }; var button = mkButton({ f: function(streams) { return { text: streams.clicked .map(constant(1)) .scan(0, function(a, b) { return a + b; }), }; }, }); $('body').append(button.el); }); 例: Counting Button 副作用 (値変化時の処理登録、 テキストの中身を変更) 副作用 (DOM 要素登録)
  28. 28. 例: Cross Counting Button クリックすると 反対側が増える
  29. 29. 例: Cross Counting Button クリックすると 反対側が増える Property を作るために、 別の button の EventStream が必要 (相互に要求)
  30. 30. 例: Cross Counting Button クリックすると 反対側が増える Property を作るために、 別の button の EventStream が必要 (相互に要求) Bus を使い、 一旦 EventStream として渡しておき、 button を作るときに Bus につなぐ
  31. 31. 例: Cross Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el); });
  32. 32. 例: Cross Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el); }); button から出る EventStream を Bus として 取得できるようにしておく
  33. 33. 例: Cross Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el); }); 先に EventStream だけ 取得しておいて、 button から出る EventStream を Bus として 取得できるようにしておく
  34. 34. 例: Cross Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el); }); 先に EventStream だけ 取得しておいて、 EventStream から Property を作成 button から出る EventStream を Bus として 取得できるようにしておく
  35. 35. 例: Cross Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el); }); button から出る EventStream を Bus として 取得できるようにしておく 先に EventStream だけ 取得しておいて、 一緒に EventStream (Bus) を渡す EventStream から Property を作成
  36. 36. 例: Cross Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el); }); 渡された Bus に、 クリックの EventStream をつなげる
  37. 37. 例: Cross Counting Button $(function() { var constant = function(x) { return function() { return x; }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var streams1 = mkButton.streams(); var streams2 = mkButton.streams(); var logic = function(s) { return s.clicked.map(constant(1)).scan(0, function(a, b) { return a + b; }); }; var button1 = mkButton.create({ properties: { text: logic(streams2) }, streams: streams1, }); var button2 = mkButton.create({ properties: { text: logic(streams1) }, streams: streams2, }); $('body').append(button1.el).append(' ').append(button2.el); }); 副作用 (値変化時の処理登録、 テキストの中身を変更) 副作用 (DOM 要素登録) 副作用 (Bus につなぐ)
  38. 38. リクエスト処理
  39. 39. Bacon.fromPromise • jQuery の ajax 系メソッドが返 す Promise オブジェクトから、 EventStream を作る。
  40. 40. Bacon.fromPromise • jQuery の ajax 系メソッドが返 す Promise オブジェクトから、 EventStream を作る。 assign は、イベント登録 を解除する関数を返す 流れてきたレスポンス
  41. 41. • レスポンスがエラーの場合、そのままでは assign 等に流れていかない。 エラーレスポンスの処理
  42. 42. エラーレスポンスの処理 • レスポンスがエラーの場合、そのままでは assign 等に流れていかない。 • mapError メソッドを使って、エラー系の流れを変換関数を通して本流へ流 す。 流れてきたエラーレスポンス エラー系の流れをそのまま本流へ
  43. 43. EventStream#flatMap/flatMapLatest • EventStream が流れてくる EventStream を、中の EventStream に流れる値が 流れてくる EventStream にする 引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams 引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams flatMap flatMapLatest
  44. 44. EventStream#flatMap/flatMapLatest • EventStream が流れてくる EventStream を、中の EventStream に流れる値が 流れてくる EventStream にする 引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams 引用: https://github.com/raimohanska/bacon.js/wiki/Diagrams flatMap flatMapLatest 全部流す 後の結果の方が速ければ、前の結果は流れない
  45. 45. リクエスト処理の例 右に入力したパスに GET リクエストを飛ばす 返ってきたレスポンスを表示
  46. 46. ヘルパ var constant = function(x) { return function() { return x; }; }; var id = function(x) { return x; }; var left = function(x) { return function(f, g) { return f(x); }; }; var right = function(x) { return function(f, g) { return g(x); }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el }; };
  47. 47. ヘルパ var constant = function(x) { return function() { return x; }; }; var id = function(x) { return x; }; var left = function(x) { return function(f, g) { return f(x); }; }; var right = function(x) { return function(f, g) { return g(x); }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el }; }; Either
  48. 48. ヘルパ var constant = function(x) { return function() { return x; }; }; var id = function(x) { return x; }; var left = function(x) { return function(f, g) { return f(x); }; }; var right = function(x) { return function(f, g) { return g(x); }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el }; }; ボタンの 有効/無効 も Property で受け取る
  49. 49. ヘルパ var constant = function(x) { return function() { return x; }; }; var id = function(x) { return x; }; var left = function(x) { return function(f, g) { return f(x); }; }; var right = function(x) { return function(f, g) { return g(x); }; }; var mkButton = { streams: function() { return { clicked: new Bacon.Bus() }; }, create: function(conf) { var el = $('<button />'); conf.properties.text.assign(function(text) { el.text(text); }); conf.properties.enable.assign(function(enable) { el.attr('disabled', !enable); }); conf.streams.clicked.plug(el.asEventStream('click')); return { el: el }; }, }; var mkTextarea = function(conf) { var el = $('<textarea />'); conf.val.assign(function(text) { el.val(text); }); return { el: el }; }; 出力用テキストエリア 設定は出力値 Property のみ。
  50. 50. 実装 var input = $('<input type="text" />').width(400); var bs = mkButton.streams(); var request = bs.clicked.map(function() { return input.val(); }); var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); }); }); var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs, }); var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(), }); $('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200)));
  51. 51. 実装 var input = $('<input type="text" />').width(400); var bs = mkButton.streams(); var request = bs.clicked.map(function() { return input.val(); }); var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); }); }); var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs, }); var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(), }); $('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200))); ボタンがクリックされると 入力値が流れる EventStream
  52. 52. 実装 var input = $('<input type="text" />').width(400); var bs = mkButton.streams(); var request = bs.clicked.map(function() { return input.val(); }); var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); }); }); var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs, }); var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(), }); $('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200))); 入力が流れてきたら、 リクエストを飛ばす
  53. 53. 実装 var input = $('<input type="text" />').width(400); var bs = mkButton.streams(); var request = bs.clicked.map(function() { return input.val(); }); var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); }); }); var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs, }); var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(), }); $('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200))); 正常レスポンスを Right で包んでおいて、
  54. 54. 実装 var input = $('<input type="text" />').width(400); var bs = mkButton.streams(); var request = bs.clicked.map(function() { return input.val(); }); var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); }); }); var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs, }); var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(), }); $('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200))); 正常レスポンスを Right で包んでおいて、 エラーレスポンスは Left で包んで本流へ
  55. 55. 実装 var input = $('<input type="text" />').width(400); var bs = mkButton.streams(); var request = bs.clicked.map(function() { return input.val(); }); var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); }); }); var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs, }); var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(), }); $('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200))); レスポンスが返ってくるまでは ボタンを無効にする
  56. 56. 実装 var input = $('<input type="text" />').width(400); var bs = mkButton.streams(); var request = bs.clicked.map(function() { return input.val(); }); var response = request.flatMapLatest(function(url) { return Bacon.fromPromise($.get(url)).map(right) .mapError(function(e) { return left(e.responseText); }); }); var button = mkButton.create({ properties: { text: Bacon.constant('request'), enable: Bacon.once(true) .merge(request.map(constant(false))) .merge(response.map(constant(true))) .toProperty(), }, streams: bs, }); var output = mkTextarea({ val: response.map(function(r) { return r(function(msg) { return 'error - ' + msg; }, id); }).toProperty(), }); $('body') .append($('<p />').append(input).append(' ').append(button.el)) .append($('<p />').append(output.el.width(500).height(200))); 正常レスポンスはそのまま出力 エラーレスポンスは ‘error - ’ に続けて出力
  57. 57. 設計 • 実際には、streams から properties を作る部分や、リクエストを飛ばす部分 は、別モジュールにしておくといい。 ViewLogic Storage Ajax App get streams, create from properties create properties from streams and storages create response-streams

×