継続周辺の概念を俯瞰する
継続はEffect系の理論の基礎になっている(?)割に、理解するまでに必要な道筋が多くて難しい 見る記事によって用語の揺れがあることもあるので、その辺も調整しつつ語彙の俯瞰をする
概念Aの説明に概念Bが出てきて、というのが連鎖しがちなので、順序をある程度定めておきたい
継続以前の基本的な用語
関数が呼び出されるときに積まれるスタック
文字通り、関数の末尾で何らかの関数を呼び出すこと
文字通り、末尾呼び出しを除去し、コールスタックの積み上げを防ぐ
末尾呼び出しの特に再帰になっているケース
末尾再帰での、末尾呼び出しの除去
普通の末尾呼び出しの除去と異なり、Loopに変換できたりする
継続の勉強のためにGaucheに慣れる
Lisp方言の一つであるSchemeの処理系
Gaucheを知らなくても継続を学ぶことは出来るが、言語仕様として継続を要請していることもあり、Gaucheでの資料が多い
Lips構文がわからないとそういう資料も読めない
継続を学ぶ際にSchemeが適している理由など
S式とラムダ計算の調和性
動的型である
副作用を持つ
global変数に継続を代入する、などができる
継続の概念の基礎
処理全体を時間軸で見た時に、ある点における継続とは、「その点以降の処理のこと」
全ての関数呼び出しを継続渡しにしたもの
継続渡しの特殊版
理解のために書いたやつmrsekut.icon
「部分継続」も同じものを指す
継続はその点以降の未来全てを扱うが、それでは範囲が広すぎるのでもう少し範囲を狭めた未来を取り扱う
「区切りを入れる関数」と「部分継続を取り出す関数」の2つの関数で操作する
代表的なものがresetとshift
継続の応用
圏論に繋げる
論理学に繋げる
CPSと直観主義論理の関係
CPSを使って再帰を末尾再帰に変換する
CPS の便利な使い方
これ駆動で見ていくと良いかもしれない
lispに慣れていないhaskellerなら、むしろこっちでやった邦画で理解できるのでは?を検証するmrsekut.icon
なんか微妙な気もする
ノイズが多い感じがある
けっきょく継続って何がすごいのか?
「HaskellにおけるMonad」と同じように、言語仕様に組み込まれていることで、
「構文を追加せずにいろいろなことができる」が実現できる、ということ?
まあ大体そんな感じだと思うmrsekut.icon
継続というかcall/ccがすごすぎる
この記事はcall/ccがなんでもできすぎるので逆に良くないみたいな感じのことが書かれていて、最初の2パラグラフぐらい読んだらcall/ccが抽象化としてすごいことがわかる call/ccで取れる継続ってどこから以降の継続?
その関数のbodyは含まれるの?
例えばこういう時、cont-procには何が入るのか
このコードではcont-procの内容をreuseに代入しているので確認できる
code:aa
(define (f x y) (* x y))
(define (g x) (+ x 100))
(define (h x) (* x 100))
(h
(call/cc
(lambda (cont-proc)
(let ((v (f 3 10)))
(set! reuse cont-proc)
(g v)
))))
(reuse 10) ; 1000
hのbodyが入ってるっぽい。
ここではreuseはhと全く同じ挙動をする
コルーチンの上位互換が継続
↓リンクがやたら多いのは、「どの文章が良い資料なのか」が不明なので、見つけたやつとりあえず全部コピっているからmrsekut.icon
教科書的な資料があれば、こんなメモは不要なんだが
例外の一般化?
どのへんが?
コルーチンの上位互換?
どのへんが?
gotoと同じ?
継続を明示的に扱う方法
全てのプログラムを継続渡しで書く
call/ccを使う
継続
CPS
CPS変換
call/cc
手続きの定義時と、呼び出し時の両方で使うケースがある
これは区別したほうが理解しやすいかもしれない、しらんけど
python
どういうこと?
goto文は、継続に実行を移すこと、と捉えればまあそうか
継続モナド
めちゃくわしい
普通の継続の話や他のモナドの話もある
スライドだけでは分かりづらいかもだが、ここでもリソース管理の話をしている
scala
call/ccは扱いづらく危険
言語処理系は限定継続をprimitiveに提供すべき
限定継続は継続よりprimitiveなもの?
限定継続があれば継続をエミュレートできる?
米田埋め込み
論理学での継続
fold
7も
理解してないときに書いてた謎のメモ
同期的なCPS
code:ts
const add = (a: number, b: number, cb: (r: number) => void) => {
cb(a + b);
};
add(1, 2, r => console.log(r));
非同期CPS
code:ts
const add = (a: number, b: number, cb: (r: number) => void) => {
setTimeout(() => {
cb(a + b);
}, 100);
};
console.log("beofre");
add(1, 2, r => console.log(r));
console.log("after");
setTimeoutが「非同期CPS」として実装されているから、非同期CPSとして定義できてる
非同期処理でもないのにCPSを使うことの嬉しさがわからない
code:hs
-- 和を求める
add :: Num a => a -> a -> a
add x y = x + y
-- addのCPS版
addCPS :: Num a => a -> a -> (a -> result) -> result
addCPS x y cont = cont (x + y)
main = do
show $ add 1 2
addCPS 1 2 $ \sum -> show sum
この例だとCPSがないバージョンのほうが理解しやすいし簡潔
最初CPSのメリットは、ライブラリが提供する関数(ここでは例えばaddやaddCPS)について「返り値をごにょれる」というのがあるのか、と思ったが、
別にCPSを使ってなくても上のコード例のようにできる、し、そっちのほうが自然
何が嬉しい
関数呼び出し後の処理を関数側が制御できる
具体的には?mrsekut.icon*6
最適化
全ての継続をインライン展開することで、関数呼び出しのオーバヘッドを消せる
Schemeでこれをやってるんだっけ、たしかmrsekut.icon
スタック使用量も減らせる
そもそもCPSを使って嬉しいのはどういうときか
ex. 非同期処理をやるとき
ex. ライブラリを実装するとき
同期処理でわざわざコールバック関数もしくはCPSを使う利点 ってあるの?
非同期処理ではわかる
CPSの関数定義者側の利点、関数利用者側の利点
コールバック関数の方が継続を包含する概念なので、コールバック関数の問題は同時にCPSの問題となり得る
algebraic effectsの記事で出てきた再開可能性は継続でできるんだっけ #?? try..catchでは一度throwしてしまうと、元の場所には戻れないが、、のやつ