React ComponentにImmutable.jsは(多分)だめ
ここにもはっきり書いてあるように,
Note that state must be a plain JS object, and not an Immutable collection, because React's setState API expects an object literal and will merge it (Object.assign) with the previous state.
React ComponentのstateをImmutableデータ構造にするのは良くない.
これをするとsetStateで渡ってくるものが(型情報的には確かにImmutable.Listでも)JSArrayになってたりする.
ググったら以下の2つの方法をとっている人が多かった.
- setState内では全ての
state.piyo
をImmutable.DataType(state.piyo)
みたいにラップする
これは一見うまく行くように見えて, たまたまラップすることを忘れたりするとImmutable.List.unshift
じゃなくてArray.unshift
が発動したりして死ぬ可能性がある.
- state一つ一つをImmutableにするんじゃなくてstateをまるごとImmutable.Mapにする
これはデータの取り出しがImmutable.Map.get("nyan")
みたいになるので(型をつけている場合は)型情報が消滅するので自明にダメ.
なお一瞬だけMapのKeyにString Literal Typeを渡すみたいなのも考えたけど, 与えられたデータ構造からString Literal Typeをメタ生成するとかできるんだろうか. できてもやりたくはないけど.
他にいい方法があるなら知りたい.
(やはりTypescriptでも型レベルMapを作るしかない)
(ていうかこんな分かりやすいトラップがあるのにImmutable.jsはReactと相性がいいって言ったやつ誰だよ)
(interfaceの貧弱っぷりを見るにつけてもTypescriptはまだまだ改良の余地アリアリな感じがする)
variable-free type theoryの圏論的な解釈について
の続き. いくつか疑問が解消されたのでそれについて.
var-free type theoryについて
- syntaxはcontext, type, term, substからなる.
- contextはtypeの有限列(通常はvariableとそのtypeの組の列だが, 今回はvariable-freeなのでtypeだけの列になる).
- typeはground typeと関数型
A → B
とsubstが代入された型(σ[s]
の形をしているもの)のいずれか. - termは
1
と代入された項M[s]
のいずれか. - substは
id
と合成f . g
とshift↑
とextension<f:subst, M:term>
のいずれか.
termは型付けをde Bruijn indexで考える.
term1
はcontextの直近のtypeに対応する項とみなす. 例えば, Γ,σ ⊢ 1:σ
などである.
substはcontextの間の変換とみなす.
例えばΓ ⊢ id:Γ
はid(Γ)=Γ
を表す. また, 合成は通常と逆向きに書くらしい(多分見やすさの都合).
shift↑
はindexをすべて1つ加算するような変換とみなす. 例えばΓ,σ ⊢ ↑:Γ
はindexが上に1つずれるので一番新しいtypeσ
を忘れるものとみなす.
extensionのtyping ruleは次のようなものである:
Γ ⊢ f:Γ', Γ' ⊢ σ, Γ ⊢ M:σ[f] ⇒ Γ ⊢ <f,M>:Γ',σ
これは, Mによってcontextに新しいtypeを付け加える操作と考えることができる.
あるいは, 変換fをMによって拡張したものとみなしてもよい.
locally cartesian closed categoryでの解釈
ML theoryはLCCCとイイカンジに対応する.
ここではcategory with attributesを使った解釈を使うことにする.
すると上で導入したsyntaxはLCCC(cwa)でいい感じに解釈できる.
- contextはobject, context
Γ
のtypeはΓ
-cod morphismとみなす. - type Aをもつtermは, Aに対応するmorphism(shift)のsection.
- substはmorphismとみなす.
以上の対応で, 上のsyntaxはcwaの構造で超いい感じになる.
- function type
→
はPi-typeを使って定義するので一旦忘れる. (論文にあるようなcwaを構成するとdependent product/sum, extensional identity typeを持つ) - subst
id
はidentity, 合成はcompositionになる. - shift
↑
はcwaのcanonical projection operatorになる. - 代入された項
M[s]
(M:Γ → Γ,A
,s:Γ'→Γ
)はpullbackΓ'×Γ,A[s]
overΓ
のUMPから誘導されるmorphism - extension
<f,M>
はpullbackの対角線にあたるmorphism(Mとfのliftを合成する). 1=<id,id>
はpullbackのUMPからつくる.
Set model
上のだけだとよく分からないのでSetで解釈する.
- shiftはshift写像
<gi,a:A> ~> <gi>
- termはこのsectionなので, A型の値の追加,
<gi> ~> <gi,a:A>
になる. これはa:A
と同一視できる. - 代入項に登場するpullbackは
Γ'×Γ,A[s] = {(gi', gi, a) | s(gi')=gi, a=M} = {(gi',M)}
と同一視できる. - よって
M[s]
は<gi'> ~> (<gi'>,M)
とみなせる. - extensionは
<f,t:A> = ↑*f . t ; <gi'> ~> <gi',t> ~> <f(gi'),t>
になる.
特に特徴的なのはextensionの拡張で, fによる変換をtで拡張したもの, i.e. fの変換に加えてtの値を写像に追加するものと考えることができる.
extensionっぽさがあってとてもよい.
余談
もともとML type theoryのcwaでの解釈の話を調べていてvar-free syntaxが出てきたので何かしら関係はあるのだろうと思っていたけど, ちゃんと直感的にいい感じに対応していて超すっきりした.
ついでに, 普通のλ計算とvar-freeなλ計算の変換を具体的に構成したい.
さらに気が向いたらproof formalizationもしたい.
参考文献
- https://ncatlab.org/nlab/files/CurienGarnerHofmann.pdf variable-free syntaxの出処
- http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.54.4410 category with attributesによる解釈
- http://iso.mor.phis.me/archives/2011-2012/stage-2012-goteburg/report.pdf extension, shiftの記法や圏論での解釈のアイデアは主にここから
データ型のCPS変換について
HaskellでCPS変換とか調べているとデータ型のCPS変換というのが出てくる.
関数のCPS変換は継続を引数に追加して末尾再帰の形にすればだいたいOKなのでまあわかるのだけど, データ型のCPS変換というのは何なんだという話.
継続は何を継続しているのかがいまいち分からないので何故そのデータ型はCPS変換するとその型になるんだと不思議に思っていたのがちょっと解決したのでそれについて書く.
関数 is っょぃ
パフォーマンスのことを考えれば, 関数型と基本型(ground type)が型の中では圧倒的に偉い.
基本型というのはまぁproductとかrecursionとかdependent typeとかを含まない基本的な型のこと. 例えばIntとか(Haskellのような言語ではIntというのは計算機の都合上特別扱いされているので, ここでのIntはN×Nにring構造を入れたもののことではない).
基本型は基本的にコンパイラのサポートが受けられるので特別な形で処理されるから処理は速い.
関数型は作ったり壊したり(introしたりelimしたり)が速いので速い.
その他の型, つまりproductとかcoproductとかなんかそういうのが遅いのはコンストラクタに対するパターンマッチを何度も繰り返すからで, パターンマッチをさせない書き方ができれば処理が速くなる可能性がある.
そこでデータ型を関数型と基本型の組み合わせでそれと同型なものに変換できない? という発想になる.
Cont iso
初めに, 次が成り立つ:
型Aに対し, Aと∀P. (A -> P) -> P
はiso.
(Prf) a:A ==> λ(f:A->P). f(a):P
, h:(A->P)->P ==> h(id:A->A):A
は同型を与える.
この対応は(名前があるのか知らないけど)至るところに現れる.
例えばP=bottomの時は右辺の型は¬¬A
のことだし, ContモナドはCont r a = (a -> r) -> r
だったし, そもそもA -> (A -> P) -> P
は単なる関数適用のflip版のことだった. さらに言えば, この同型はyoneda lemmaのId functorに適用した形になっているとも言える.
Contモナドの名前的に明らかにこのことは継続とかそういうのと関係しているのだけどこれだけでは何のことかはわからない. わからないのでもう少し話を進めてみることにする.
直和型
A+B
の直和型を特徴づけるものといえば当然elim ruleになる.(何故かは下で分かる)
というのも, 直和型のelim ruleはちょうどcoproduct図式のUMPをもつ射に対応する.
A+Bのelim rule: ∀P. (A -> P) -> (B -> P) -> (A+B -> P)
これは次と同値:
A+B -> ∀P. (A -> P) -> (B -> P) -> P
偶然(要出典), 最初にやった同型と同じ * -> (∀P. (..) -> P) の形が出てきた.
で, coproductはup to isoに決まるので∀P. (A -> P) -> (B -> P) -> P
はそもそもA+B
と同型になる.
これによって次のことがわかる.
A+B = (∀P. (A -> P) -> (B -> P) -> P)
と定義してよい. 右辺はパターンマッチが起こらないので速い.
elim ruleとは何か
で, このelim ruleとは何なのかという話だけど, これは次のようにして導出される:
A+B = ∀P. (A+B -> P) -> P : 最初の同型 = ∀P. hom(A+B, P) -> P = ∀P. hom(A,P) × hom(B,P) -> P : homはcoproductをproductに変える = ∀P. (A -> P) × (B -> P) -> P = ∀P. (A -> P) -> (B -> P) -> P : 直積と冪の随伴(curry化)
一般に, inductive datatypeというのはelim ruleが本質的に効いてくる.
(これはinductionがelim ruleになってるということを表している)
その場合は上のような変換で簡単な形に変形できる.
直積型
直積は上の双対なのでintro ruleが対応するけれど, こちらは上のようなほしい形と一致しない.
けれど, なんと直積はCCCでは(後で補足する)左随伴なのでいい感じに変形ができる.
A×B = ∀P. (A×B -> P) -> P = ∀P. (A -> B -> P) -> P : 直積と冪の随伴(curry化)
例: List
あとは上のことを使って機械的に変形するだけで単純な形のデータ型なら大体いい感じの型が作れる.
例) List
[a] = 1 + a × [a] = ∀P. ((1 + a × [a]) -> P) -> P = ∀P. ((1 -> P) -> (a × [a] -> P) -> P) -> P : coproductのPR変換 = ∀P. ((1 -> P) -> (a -> [a] -> P) -> P) -> P : productのPR変換 = ∀P. (P -> (a -> [a] -> P) -> P) -> P : CCCならhom(1,P)=P
この定義でhead, tailなどは簡単に実装できる.
まとめ
以上により, データ型はパターンマッチを含まないより速い形に書き換えられることがわかった.
このようにして書き換えたものの中で, 継続渡しっぽさを含むものをCPS変換されたデータ型と呼ぶっぽい.
(ちなみに, (... -> P) -> P
が割と継続渡しっぽさがある)
ちなみにこのようなぽさを含むもの全体が上で変換された全体と一致するかどうかは私は知らない.
軽いまとめ:
- 直積とか直和はいい感じに変形できて便利.
- 関数型がめっちゃ偉い.
- 上の記述はだいたいHaskellとかで考えていたけれど, CCCな型システムを持つ言語なら同じことが出来ると思う
Haskellの型システムってCCCじゃないですよね????*1
ヤ,ヤメロォ〜〜〜(今回はundefinedとかいう邪悪なものは考えてない)
はじめに
初めに書きたかったことを書く.
CPS変換でググると, CPS変換とは継続をうんたらかんたらとかいう文章がヒットしまくるけど何の説明にもなっていないということがすでに人生で5億回くらいあった.
一応自分の中では「関数の」CPS変換と「データ型の」CPS変換という2種類の言葉があり, 前者はContモナドを使った書き方で後者は今回説明したようなことに対応しているのだろうという理解でいる.
本来のCPS変換は前者で, 前者は主に末尾再帰とかそういう話だと思うんだけど, HaskellとCPS変換の話をググると(他の言語でもそうかも?)後者のCPS変換も結構ちょくちょく出てくる.
とりあえずCPS変換の言語に依らないちゃんとした定義を発見するか自分で作ることができれば, この腑に落ちない感じも少しは改善するのかも.
あと, 上のことはCPS変換と何の関係もないかもしれない.
(CPS変換の定義が分からないので関係があるかどうかもわからない)
でも上の操作でパターンマッチは確実に減るので, まあ誰かの役に立てばいいなと思いました.
参考文献
- http://www.brics.dk/RS/01/49/BRICS-RS-01-49.pdf 読んでないけどたぶんCPS変換の真面目な定義が書いてありそう?