Skip to content

Instantly share code, notes, and snippets.

@ken-okabe
Last active August 29, 2015 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ken-okabe/9ea4ac850c71210a3108 to your computer and use it in GitHub Desktop.
Save ken-okabe/9ea4ac850c71210a3108 to your computer and use it in GitHub Desktop.
数学的FRP解説からのFacebook-React
##リアクティブの説明
もっとも身近でわかりやすい説明とは、以下のようなものです。
車輪の再発明をする必要は感じられないので、
@hirokidaichi 氏による解説
[2015年に備えて知っておきたいリアクティブアーキテクチャの潮流](http://qiita.com/hirokidaichi/items/9c1d862099c2e12f5b0f)
から引用させていただきます。
>## エクセルとリアクティブプログラミング
リアクティブプログラミングについて、説明するときに「Excel」みたいなものだよとよく言われます。以下のシートをみてください。(=は抜いてあります。)
![スクリーンショット 2014-11-26 18.20.08.png](https://qiita-image-store.s3.amazonaws.com/0/35671/9f8418b7-9bb0-9e49-6eaf-7bcb6d57d2ad.png "スクリーンショット 2014-11-26 18.20.08.png")
>>このようなシートがあったときに、A1の値を書き換えたらどのように挙動するでしょうか。
![iHGy0scVXN.gif](https://qiita-image-store.s3.amazonaws.com/0/35671/bd550dfc-42f3-26e9-602a-936df780710f.gif "iHGy0scVXN.gif")
>セルの各値は、再計算を指示することなく、値同士の関係性のみを記述しただけで自動的に再更新されます。
>次に既存のプログラミングパラダイムで、値同士の関係を記述した場合を考えてみましょう。
```
var A1 = 1;
var B1 = A1 + 1;
var C1 = B1 + 1;
var A2 = sum(A1,B1,C1);
var B2 = A2 * 2;
```
>このような処理があったときに
```
A1 = 2;
```
>としても、B1,C1,A2,B2の値は変化せず、A1のみが変化します。
既存のプログラミングパラダイムでA1とB1の関係を記述する場合、observerパターンなどを使い、次のように書く必要があるでしょう。
```
var A1 = new Cell;
var B1 = new Cell;
A1.on("change",function(e){
B1.setValue(e.value + 1);
});
```
>リアクティブプログラミングを言い換えるのであれば、Observerパターンを意識させず関係性のみを記述することともいえるでしょう。
>このようなパラダイムの言語として代表的なものはハードウェア記述言語であるVerilogなどが挙げられるでしょう。電子回路のような物理的パスと信号を取り扱うシステムは、データフローとイベントを取り扱うアーキテクチャと極めて親和性があります。
##表計算とリアクティブ
「Excel」という**表計算アプリ**の例が、
**リアクティブプログラミングの概念**を説明するときに
よく用いられるというのは、何気に本質的で、
表計算なんていう**「論理」を網羅するようなアプリケーション**
と**リアクティブは非常に親和性が高い**、ということです。
##もしもJavaScriptがリアクティブだったら?
```js
var a, b; //Excelのマス目に相当する
a = 1; //aのマス目の値は 1
b = a * 5; //bのマス目の値は、aのマス目の値の5倍と等しい
a = 2; //aのマス目の値を 2に変更
console.log(b); // bのマス目はリアクティブに再計算され
//10 が表示される!!
```
こうなるはずです。
**表計算**っていうのは、さっき書いたように
**「論理」**
を網羅するアプリケーションですが、上のコードで、
その**「論理」をリアクティブな特徴として強烈に体現している行**
がたったひとつだけありますね?
```js
b = a * 5; //bのマス目の値は、aのマス目の値の5倍と等しい
```
もちろんこれです。
これは表計算アプリのマス目に宣言されたとおりの
左辺と右辺の値が常に等しい**「数学の方程式」**なんです。
つまり**純粋な「論理」**です。
これが、リアクティブプログラミングが
関数型プログラミングと非常に相性が良ろしく、
関数型リアクティブプログラミング(Functional Reactive Programming)
と統合(フュージョン)されてしまう最大の理由です。
##破壊的代入による論理破綻と時間変化
ただし、
```js
var a, b; //Excelのマス目に相当する
a = 1; //aのマス目の値は 1
b = a * 5; //bのマス目の値は、aのマス目の値の5倍と等しい
a = 2; //aのマス目の値を 2に変更
console.log(b); // bのマス目はリアクティブに再計算され
//10 が表示される!!
```
このコードで「論理」もへったくれもない行がひとつだけありますね。
```
a = 2; //aのマス目の値を 2に変更
```
こいつです。
数学の方程式だ、論理だ、というのであれば、
$$
a = 1
$$
$$
a = 2
$$
は同時に成り立たない。
数学の世界にデフォルトで「時間の流れ」みたいなものは
表現されませんから、このコードは論理破綻しているわけです。
論理破綻なんかしていない!とフツーに思ってしまうのは、
無意識にコードの上のほうとコードの下のほうでは、
時間が異なっていて、命令実行される「前」と「後」という
時間変化を暗黙の了解としてしまっているからです。
`a = 1`
であるところに、無造作に
`a = 2`
としてしまうのは**論理を破壊**するので、
プログラミングでは**破壊的代入**と呼ばれており、
こういう操作は**関数型プログラミングではNG**です。
##変数にはきちんとインデックスをふる
では、どのようにして論理的整合性を取るのか?というと、
**変数にきちんとインデックスを振り分けてやって**
**別の独立した存在として扱う。**
この場合、時間変化するのだから、
インデックスを`time`の頭文字をとって
$$
t
$$
としてやることにしましょう。
任意の時間` t` における
$$
aとb
$$
は、
$$
a_tとb_t
$$
と表現することにします。
これで、**時間変化にともない自動的にtの値が異なる**ので、
たとえ**破壊的代入をしようとおもっても絶対に無理**になりました。
破壊的代入っていうのは、「後から」代入し直すという行為なので、
「後」ってことは、tの時間が自動的に異なるからです。
$$
a_tとb_t
$$
は、時間変化から完全に自由になり、
完全に論理的整合性のある存在に変貌したのです。
同じように、
$$
b_t = a_t \times 5
$$
というのも時間tにおけるaとbの関係性、ということで、
意味がより明確になりました。
実際これは、
「時間変化`t`において、リアクティブである」
というのを数学的に厳密に表現している式です。
同様に、
$$
console.log(b_t);\\
$$
となります。
これは、
「時間変化`t`において、
bのマス目はリアクティブに再計算されて再描画される」
というのを数学的に厳密に表現している式です。
##もしもコードを数学にする
材料は全部出揃ったわけで、
最初のコード
```js
var a, b; //Excelのマス目に相当する
a = 1; //aのマス目の値は 1
b = a * 5; //bのマス目の値は、aのマス目の値の5倍と等しい
a = 2; //aのマス目の値を 2に変更
console.log(b); // bのマス目はリアクティブに再計算され
//10 が表示される!!
```
を数学的に厳密に書き換えると、
$$
b_t = a_t \times 5\\
$$
$$
console.log(b_t);\\
$$
$$
a_0 = 1\\
$$
$$
a_1 = 2\\
$$
となります。
##数学をリアルJavaScriptコードにする
この数学を、実際のJavaScriptに再展開してやりたいわけです。
そこで、
[【後編】2015年、脱オブジェクト指向時代にFacebook-Reactのコードをイミュータブルな宣言型でイベント駆動するFRPで書く 論理の物質化をするな!](http://qiita.com/kenokabe/items/385a80295046fb9ada6d)
の後のほうでも出した、
筆者のコンセプチュアルなFRPライブラリを適用します。
SpaceTimeLine A conceptual FRP model library
GitHub
https://github.com/kenokabe/spacetimeline
npm
https://www.npmjs.com/package/spacetimeline
すると、こう書けます。
$$
a_t
$$
は、`var a = _();` で表現します。
$$
b_t = a_t \times 5
$$
は、`var b = a.map(function(n){return n * 5;});`
で表現します。
$$
console.log(b_t);
$$
は、`b.compute(console.log);` で表現します。
**ここはExcelのマス目の自動再計算の再描画宣言に相当します。**
最後に、
$$
a_0 = 1\\
$$
$$
a_1 = 2\\
$$
時間軸において、コードが解釈実行された時刻で`appear`する。
以上、全部合わせると、
```js
var _ = require('spacetimeline');
var a = _();
var b = a.map(function(n){return n * 5;});
b.compute(console.log);
a.appear(1);
a.appear(2);
```
コードの中核部分は、
```js
var a = _();
var b = a.map(function(n){return n * 5;});
b.compute(console.log);
```
この3行です。
- `a`の宣言
- `b`の宣言(`a`との論理関係を宣言)
- 最後のコンソールへ再描画する宣言。
この3行だけです。
##Facebook-Reactにする
$$
a_tとb_t
$$
は、**時間変化から完全に自由になり、**
**完全に論理的整合性のある存在**に変貌しました。
**これがそのままFacebook-Reactのコンポーネントになります。**
$$
a_t
$$
は、`TodoList`
$$
b_t
$$
は、`TodoApp`
$$
console.log(b_t);
$$
は、
筆者のさっきのFRPライブラリでは、
`b.compute(console.log);`
でしたが、
Reactではもちろん、
`React.render(<TodoApp />, mountNode);`
になります。
**ここはExcelのマス目の自動再計算の再描画宣言に相当します。**
コードの中核部分はこの3行です。
- `TodoList`の宣言
- `TodoApp`の宣言(`TodoList`との論理関係を宣言)
- 最後のDOMへ再描画する宣言。
この3行だけです。
Facebook-ReactのToDoリストアプリのコードは以下です。
http://facebook.github.io/react/
よーくみると、
- `TodoList`の宣言
- `TodoApp`の宣言(`TodoList`との論理関係を宣言)
- 最後のDOMへ再描画する宣言
のたった3行だけで出来ており、
さっきの筆者のFRPのコード
- `a`の宣言
- `b`の宣言(`a`との論理関係を宣言)
- 最後のコンソールへ再描画する宣言。
とまったく同じ構造になっていますね?
```js
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.render(<TodoApp />, mountNode);
```
おわり。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment