状態は現実であり武器でありバグの温床.
共にやっていくには知識が必要.
言葉
- 状態 / state
- 状態を持つ / stateful 1
- 状態を持たない / stateless
状態とは何か
なにであるか
- 変化しうる値
- 参照"される"値
独立した値
- 何かから導き出されるものではない
1つの対象、複数の状態
- (1つの対象がカギ)
明示的状態、暗示的状態/宣言的状態
5分でわかる、プログラミングの潮流 - Qiita
プログラマが書いて操作する状態: 明示的状態
処理系/ランタイムが自動で操作する状態: 暗示的状態
プログラマからみて「明示的」「暗示的」
blackbox・抽象化ってこと.
状態管理サービスに状態管理はお任せ、ともいえる.
型状態指向プログラミング
状態の変化がその言語の第一級オブジェクト
コーディングに対する考え方を変える6つのプログラミングパラダイム | POSTD
状態を管理する作業はプログラマの手を離れ、言語処理系がその役割をする。
データフロープログラミング - Wikipedia
いつ保存される
multi-sourceだと必要になる.
single-sourceなら「更新=新規全情報」なのでstateいらず.
またsingle-sourceでも「前状態に基づいた変更」だとstateを保存しておく必要がある.
なにでないか
実装(変数はポインタ実装の抽象化)
使い回されるポインタとは違う.
ポインタをどう使うかはプログラマの自由.
なんで状態が存在する?
使える。
時間に沿って変化する値を、ある1つの識別子でアクセスできる.
状態の危険性
不要な状態(悪い習慣)
安易な状態のコピー(キャッシュ)
時間と共に変化するもの
c.f. コンピュータプログラミングの概念・技法・モデル
状態を取得する
参照を持っている -> 参照問い合わせコスト
コピーを持っている -> 同期
操作
- plane object: なんでもあり
- class: stateとstate変更を同じ箱にいれることで操作方法を限定.
- classの問題点
- class内部ではいじり放題
- classの問題点
- fp: あらゆる関数を渡すことで操作
Clojure
"Values and Change: Clojure’s approach to Identity and State" Clojure - State
"変わるけど同じもの"と認識する(連続性の認知)
とる値: state
While some programs are merely large functions
...
many others are not
- they are more like working models, and as such need to support "identity".
a stable logical entity associated with a series of different values over time
identity
value
valueであるべきものまでidentityにしてる.
"value"のaliasが"state"
identity has a state at any point in time
identity/value association
作るべき以上のidentityを作成している.
all but the most disciplined programmers to create many more identities than they should
We need to move away from a notion of state as
not good: "the content of this memory block" good: "the value currently associated with this identity".
状態そのものは固定値.
identityに結びついている状態は時間によって異なる.
値とは
1は値.
1は2にもなります(!?)
=> 値は変わらない
Qs
2-way bindingってどこがstateなんだ?
stateはstateとして明示して扱うのが良い(変数は気軽にstateとして使いたくなっちゃう)。[制約による質の向上]
c.f. behavior/stream of FRP
一時変数の使い方に注意しましょう
バグを生まないプログラミングのために · GitHub
暗示的状態
Reduxは変数の更新等を抽象化してくれる.
プログラマはactionとactionに対する応答を定義するだけ. 変数を触らない.
うまい扱い方
pattern 1
状態を局所へ局所へ寄せていく。
寄せた先でどう扱うのか.
「宣言的」「データフロー宣言」「処理系/ランタイム」
宣言部分は関数で宣言することになる。
値の変換を宣言 = 関数を宣言
状態の変化が伝播(再計算)を引きおこすだけ
関数でうまーく作れれば、本当にただmain関数に新しい状態を渡して実行するだけ.
無関係
for(let i=0; i<10; i++){ // processing }
バグの元:
- i==0って書き間違う
- i<10? i<=10?
- i++なのか、i--なのか、 (i=i+2)なのか
- processing中でiに干渉
- loop対象の部分削除を始めたらもう大変
#
中では状態をガンガン使っている。
function filter<T>(array: <T>[], eval: (elem:T) => boolean) => { let filtered = []; for (let i = 0; i<eval.length; i++){ if (eval(array[i])){ filtered.push(array[i]); } } return filtered; }
でもfilter関数は array to arrayの関数でしかない.
filterが処理系で定義されていれば、状態は暗示的.
UIとのからみ
React
ReactDOM.render関数って何回呼んでも問題ないのかな?
=> 問題ない。差分更新される。
React DOM は要素とその子要素を以前のものと比較し、DOM を望ましい状態へと変えるのに必要なだけの DOM の更新を行います。
React Docs - 要素のレンダー - React は必要な箇所のみを更新する
なので関数連鎖の果てにReactDOM.renderをつないでおけばとても綺麗にいく.
状態に基づいて状態を更新する
概要
状態更新は状態のread->modify->write、すなわちトランザクションである。 single threadであっても、並行トランザクションが生まれるケースが多々ある。 並行トランザクション処理系を用いない通常のプログラミングでは種々のanomaliesが発生しうる.
状態の更新 = トランザクション
state => state
- "read" state
- "modify" state
- "write" state
まんまread-modify-write
むずかしさ
最新のstateをreadし、その最新のstateを更新しないとバグが発生する すなわち状態更新/read-modify-writeは切り離せないトランザクション. 気づかないうちに並列トランザクションを埋め込んでしまう
分岐させて両方に同じ入力 & 同じところに出力 => "last update"
むずかしさが現れるコード上の場所
共有メモリの操作 (mutableオブジェクトをconcurrentにいじいじ) メッセージ結果の統合 (immutableをconcurrentにいじいじしてmerge) 状態管理 データベース(~状態の基地)
対策
並列トランザクション+isolation level・anomaliesが状態管理の相似に使える
concurrentは避ける (single threadなコードを書く) read-modify-writeトランザクションを1セットに固める read結果の引き回しをしない asyncはトランザクション外へ制御が移るので、read-ASYNC-modify-writeとかするとnonrepetable readになるリスク高まる fetch等のasyncは、syncにフラグstateを建ててwriteまで行き、async解決時に別のトランザクションを建てるイメージで single threadならトランザクションは必ず直列になるので、トランザクションそのものを意識することが重要
- (部分的に)syncにする
- Lock: 並行の抑止
- 楽観的○○: 楽観的に処理を進めてwrite時にreadが変わってないか確認する
- eventual consistency: 完全な整合性を捨てる
(immutableなら) read-modify-writeはwriteをcommitとしてrollbackがないトランザクション、と見れる (mutableなら) modify-writeでrollbackなしなトランザクション、mutabilityからdirty write
Immutable state
stateなのにimmutable => stateの変化は値の全置換え
immutableState + pureReducer = 冪等 (mutableだと同じ変数に二度適用できない)
Create the next immutable state tree by simply modifying the current tree
immer
pureReducer :: (state, args) => state
でstate更新を行うことは、immutable stateの十分条件。
参考文献
- 関数型つまみ食い: 関数型とはプログラミング言語ではなく、プログラムデザインの問題であることに気づく
- Reduxから見えた関数型パターンの話
-
状態を抽象化して言うと、「もしオブジェクトの振る舞いがその履歴に影響されるなら、オブジェクトは 状態を持つ ( ステートフル である)。」 from Scala by Example↩