Closures vs Objects
It is well-known that closures and objects are equivalent. So are actors. Which one is the most fundamental? よく知られたように、クロージャとオブジェクトは等価です。アクターも同様です。どの概念が最も基本的なのでしょうか? A closure is a function with state in the local scope. An object also has methods and state which belong to its instance. These are compatible with each other. クロージャはローカル・スコープに状態をもつ関数です。 オブジェクトもまたそのインスタンスに属するメソッドと状態を持ちます。 これらは互いに互換性があります。 オブジェクトは複数のメソッドを持てる クロージャは関数をひとつしか返せない オブジェクトの勝ち >>3 ハッシュテーブルに関数を追加して返すことで、複数のメソッドを返すことが可能 より柔軟に、オブジェクトとそれを操作する関数の組を返すこともできるし、無限個のメソッドをもったオブジェクトだって作れる これらは書く対象によって使い分ければよいのであって、オブジェクトにメソッドが属しているのが常に最適とは限らない たとえばイベントハンドラにコールバック関数として渡すような場合は、ひとつの関数として取得できたほうが便利 クロージャのほうがprimitive 今時、高階関数やイテレータを書くのにわざわざデザインパターンとか使いたいかって話 クラスは、レキシカル環境つきオブジェクトだけが欲しくても必ず新しい名前空間が作られるし、単品で購入できない福袋みたいなもん 二変数以上の関数のほとんどは、どの引数のオブジェクトに属しているともアプリオリには言えないし、オブジェクト指向は不自然 オブジェクトと引数は別物だし、もし関数を特定のオブジェクトに属するようにしたいなら、クロージャでラップすれば簡単にできる 二変数以上の関数のほとんどは、どの引数のオブジェクトに属しているともアプリオリには言えないし、オブジェクト指向は不自然 オブジェクトと関数は別物だし、もし関数を特定のオブジェクトに属するようにしたいなら、クロージャでラップすれば簡単にできる 2変数関数をクロージャにラップして1変数関数にしても、メソッドチェーンはできないよね [1, 2, 3, 4, 5]にメソッドが属してれば [1, 2, 3, 4, 5] .filter(hoge) # ←戻り値もオブジェクト .map(piyo) # ←これも .sum() みたいにできるけど、配列がオブジェクトじゃなかったら map([1, 2, 3, 4, 5], hoge) の戻り値はただの配列なので、繰り返し関数適用するには、Lispみたいな醜い入れ子にするしかない sum( map( filter([1, 2, 3, 4, 5], hoge), piyo)) >>8-9 これでどうよ chain = (obj) => { return (f, ...args) => { if (!f) return obj return chain(f(obj, ...args)) } } chain([1, 2, 3, 4, 5])( filter, (x) => x % 2)( map, (x) =>x * x)( sum)() // => 35 一時期メタプログラミングとかいって、動的にクラスやメソッドを作れるのがすごいって言われてたけど、クロージャをデータ構造として使うならそれは自明にできることでは やり方が複数ある場合は、一番綺麗に書けるコードを採用するのが基本 入れ子にしなくても関数合成でいいのでは? メソッドチェーンっぽく書くには Reverse-application operatorってのもあるけど 個人的には関数合成のほうがスッキリしててすこ https://ideone.com/gCWxLP let hoge n = n mod 2 = 1 let piyo = ( * ) 2 let () = print_int (List.fold_left (+) 0 (List.map piyo (List.filter hoge [1;2;3;4;5]))) (* function composition *) let (<<) f g x = f (g x) let f = print_int << List.fold_left (+) 0 << List.map piyo << List.filter hoge let () = f [1;2;3;4;5] let (>>) f g x = g (f x) let f = List.filter hoge >> List.map piyo >> List.fold_left (+) 0 >> print_int let () = f [1;2;3;4;5] (* @@ Application operator *) let () = print_int @@ List.fold_left (+) 0 @@ List.map piyo @@ List.filter hoge [1;2;3;4;5] (* |> Reverse-application operator *) let () = [1;2;3;4;5] |> List.filter hoge |> List.map piyo |> List.fold_left (+) 0 |> print_int chainl = (obj) => { return (f, arg) => { if (!f) return obj return chainl(f(arg, obj)) } } cons = (x, xs) => [x, xs] foreach = (lst, f) => { if(!lst) return [x, xs] = lst f(x) foreach(f, xs) } linkedList = chainl(null)( cons, 1)( cons, 2)( cons, 3)() chainl(linkedList)(foreach, console.log)() // 3 // 2 // 1 いろいろ書けるんだな カリー化もクロージャと可変長引数があればできるね currying = (f, ...args) => (...rest) => f(...args, ...rest) これで、mapなどが map(f, col) と、関数を先にとる形でも、>>16 のchainlを用いて chainl([1, 2, 3, 4, 5])( currying(filter, (x) => x % 2))( currying(map, (x) => x * x))( sum)() と書けるわけか いや、単にchainlを chainl = (obj) => { return (f, ...arg) => { if (!f) return obj return chainl(f(...arg, obj)) } } とすればいいのか クロージャの真価はコンテキストを導入できること (let ((a 1) (b 2)) (+ a b)) ((lambda (a b) (+ a b)) 1 2) は等価 なので、上のa + bの変わりに関数を返却すれば独立したスコープの状態をもつ何かを作れる クラスベースのオブジェクト指向でも同様だが、そのようなコンテキストのテンプレート(= class)は静的にしか作れない なんでも自由を許すと無秩序になる >>10 だって、シグネチャを無視することが前提の設計になってる (最後に結果を呼び出す時は第一引数のfを省略する) TypeScriptが使われるのもそれが理由 今やPythonやRubyにも型がある 型なんて、プログラマがちまちま書かずとも、コンパイラや型チェッカが実行前に検査してくれればいいと思う currying = (f) => { args = [] apply = (a) => { if (a === undefined) return f(...args) args.push(a) return apply } return apply } クロージャはポリモーフィズムができない オブジェクト指向言語ではたとえば比較演算子の動作を型によって変えられる a.compare(b)とソースコードのどこに書いても、aの型に応じて適切にcompareの動きが選択される 変数の型によって実行する関数を変えるにはどうする? まさか関数定義に型による条件分岐をいちいち書くわけじゃあるまいし class構文が入るまでどうやってJSでクラスを定義してたのか調べれば分かるよ >>27 a.hoge()のようにしてダックタイピングするなら当然できる hogeがa(or a.prototype)に属さないただの関数の場合でも hogeの中で型による条件分岐を書く以外の方法でできるか >>26 ,28 a, bではなくcompareのほうを、状態をもった関数オブジェクトとして生成する 一例として、 compareのクロージャには、「型名=>その型の引数を受け取った時に呼び出す関数」のテーブルを保持する そのテーブルに関数を動的に登録するための関数を作成する 例: IComparable = () => { dispatch = {} compare = (self, other) => { /*dispatchテーブルから関数を検索して実行*/ } implement = (type, fn) => { /* dispatchテーブルにfnを登録 */ } return [compare, implement] } もちろんdispatchの条件は型名じゃなくても、別の判定用関数を用意してもいい インタフェースの実装は、型定義に書くよりも使用時に動的にできた方が自然だ たとえば木構造にイテレータを実装するとして、深さ優先探索にするのか幅優先探索にするのかは、その時々によって変わる。木構造の定義だけからは決まらない 最近のフレームワークはどんどん脱オブジェクト指向・関数型化してないか? Pythonのフレームワークは機能のMixinは継承ではなくデコレータ(ようはクロージャ)によるものが多い Reactなんか思想は元々関数型的だったけど、ついにクラスコンポーネントやめちゃった cps = (f) => (...args) => args.pop()(f(...args)) eq = cps((a, b) => a === b) sub = cps((a, b) => a - b) mul = cps((a, b) => a * b) fact = (n, k) => { eq(n, 0, (isZero) => { if(isZero) { return k(1) } else { return sub(n, 1, (prevn) =>{ fact(prevn, (prevf) => { mul(n, prevf, k) }) }) } }) } fact(5, console.log) Closures are functions with contexts. Objects are structures with contexts. A structure can have functions as its member, and vice versa. So both are equal. オブジェクトよりも、クロージャよりも、第一級継続のほうが強い >>37 1. ハードウェア/コンパイラの技術向上に期待する 2. ネックになる部分だけ最適化する 3. そもそもパフォーマンスが必要ならC++やRustなどで書く オブジェクトとクロージャが使えることは、コードブロック(+ ローカル変数)を第一級オブジェクトに持っているのだと思える 継続が第一級オブジェクトなのは、プログラム実行時の任意の状態が第一級オブジェクトということ >>41 昨日からどうした なにか嫌なことでもあったか? 継続はクロージャで表現されるけど、オブジェクトとクロージャが同値なら、オブジェクトによる表現もできるの? : fact dup 1 = if drop else dup 1- rot rot * swap recurse then ; 1 5 fact cr . インタフェースが静的に決まれば保守性はおちないと思う >>31 これ継承の問題点を全部解消してる上に継承よりも強力だな >>47 使いにくい上に動的ディスパッチとなっている 継承の問題点を解消している好例はRust その上で使いやすく静的ディスパッチによる単相化も可能で速い >>49 どのへんが使いにくいのかがわからないし、動的ディスパッチだと何がよくないのかもわからない Rustのほうが早いのはそりゃ当たり前 >>43 できるだろうけど、継続自体が手続き/関数的な概念なので、オブジェクトによる表現をするメリットは少ないと思う 継続とコルーチンなら、継続のほうがprimitiveなの? >>52 継続が第一級なら保存した継続を何度も呼び出せるが、コルーチンはyieldしたらもうその時点には戻れないので、継続のほうが根本的 なんでもクロージャだと戻り値全部関数だけど、型情報を持つことはできる? ディスパッチするのに必要だよね? >>54 type属性をもったオブジェクトとして返却すればいいんじゃないんですかね それを毎回書きたくないなら、生成用関数を受け取って新しい生成用関数を返す関数 makeConstructor = (f) => (type, ...args) => {type: type, value: f(...args)} みたいなのを使ってみてはどうでしょうか >>55 これ、オブジェクトからtype属性つきオブジェクトへの関数、を持ち上げる関数書けばモナドになるから自動的に変換できるんだな そもそもオブジェクトの型を動的に判定することは不可能なのだ ただのシンボル'0'が整数であるとは数学的に決して言えない 整数の性質を満たす集合に属しているから整数と言えるのだ だからオブジェクトが型を授かるためにクラスは絶対に必要なのだ そもそもふだんそんなにポリモーフィズムしてるか? 型ヒント書いたら実行前にチェックできるとか、中間コードにコンパイルされる時高速になるとか、それでよくないか? モダンなプログラミング言語 Go、Rust、Zig、Nim、Julia、Elixirなどは クラスおよびその継承を言語仕様から排除している それぞれの言語は全く異なる方針を採っているがクラス排除だけは全てで一致している クラスとその継承は悪手であるとプログラミング言語界では結論が出ている xの型がT y = Foo(x)なら yの型はFoo(T) というシステムでいいと思うの Length ::= Float Angle ::= Float Point ::= Tuple(Float, Float) Triangle ::= Triangle(Length, Length, Length) | Triangle(Length, Length, Angle) | Triangle(Length, Angle, Angle) | Triangle(Point, Point) >>57 前半はそのとおり だがクラスは不要 変数の出自が辿れればいい Natural = fn [0] 0 [n: n >= 0] n end succ = fn [Natural(n)] Natural(n + 1) end add = fn [n, Natural(0)] n [n, succ(m)] succ(add(n, m)) end mul = fn [n, Natural(0)] Natural(0) [n, succ(m)] add(mul(n, m), n) end AdditiveMonoid = Monoid() AdditiveMonoid.implimentsIdentity(Natural, fn [] Natural(0) end) AdditiveMonoid.implimentsAppend(Natural, add) MultiplicativeMonoid = Monoid() MultiplicativeMonoid.implimentsIdentity(Natural, fn [] Natural(1) end) MultiplicativeMonoid.implimentsAppend(Natural, mul) たしかに「この関数から返ってたらこの型」というのはそうか インタフェース型を返す関数から返った値は、その実体がなんであってもインタフェース型として受け取らないといけないのか Pythonの型ヒントも、内部的にやってることは型の階層構造を保存したオブジェクト作ったりしてるだけだし、型をクロージャで表すというのは十分に現実的 なんでもクロージャでは構造に基づくサブタイピングができないから、ポリモーフィズムするなら必然的に名前に基づくサブタイピングをする必要がある 型注釈はクロージャで実現できるけど、肝心の個別のオブジェクトに型を付与するには、処理系に手加えないと無理だね ただのクロージャにこれはLinkedListだみたいな型情報をつけるなら JavaScriptのようにすべてのオブジェクトがフィールドを持てるようにして、クロージャもtype属性みたいなのを持てるようにする Pythonのように__call__()メソッドを実装すればオブジェクトも関数として呼び出せるようにして、オブジェクトにラップする おとなしくにハッシュテーブルにラップするとかして、関数としてのインタフェースを保つのは諦める 型定義 Counter = Type(fn [n] fn [i] n += i end end) f = Counter(0) f is Counter # true f(1) # 1 f(2) # 3 f(3) # 6 エイリアス Adder = Counter f is Adder # true 多相型 Pair = Type(fn [T, S] fn [t: T, s: S] (t, s) end) p = Pair(Int, Str)(1, 'Hello') p is Pair(Int, Str) # true クロージャはその外の変数をキャプチャできてこそ本領を発揮する 実行のたびに無名関数を生成すると Pair(Int, Str)の実体が毎回変わってしまう 同じ型として扱うならキャッシュしとかんといかん Foo()の結果がFoo型になるので、自然にヒエラルキー Pair(Int, Str) ⊂ Pair ⊂ Type ができる 子はすべての親を知っているが、親は直接の子しか知らない ユニオン型 Male = Type() Female = Type() Sex = Male | Female 代数的データ型 Nil = Type() Cons = Type(fn [T] fn [x: T, xs: List(T)] (x, xs) end end) List = Type(fn [T] Nil | Cons(T) end) c = Cons(Int)(1, Nil()) c is Cons(Int) # true nl = Nil() nl is Nil # true nl is List(Int) # ( = Nil | Cons(T) ) true l = Cons(2, c) l is List(Int) # true クロージャは (1) 評価可能な任意のコードブロックをオブジェクト化できる (2) 新しいコンテクストを導入できる (3) 評価を遅延可能 (4) 即時生成可能 クロージャは、評価可能な任意のコードブロックを第一級オブジェクトとして扱える 継続は、プログラム実行時の任意の状態と処理を第一級オブジェクトとして扱える >>75 汎用的な制御構造として使うには難解すぎること 言語の高速化は処理系の仕事なのだから、特定の処理系で実行速度を最適化するために冗長な書き方を強いられるのは馬鹿げている >>80 たとえばコルーチンとか、非決定的なバックトラックとかなら >>79 例外処理こそ継続とパターンマッチを使うべきだよなあと思う >>81 Pythonでいうとこのジェネレータ内包表記みたいなのがあれば、多くの場面で十分なのかな >>84 コルーチンの中でコルーチンを呼び出したら、yieldしたら最上位の呼び出し元まで返ってくるのが望ましい >>85 非同期コルーチンAの中で非同期コルーチンBを呼ぼうとしたら自動的にAはyieldして戻ってからコルーチンBを呼び出し そのBについても全て終えるか他コルーチン呼び出しで自動的にyield それぞれyieldして待機中の非同期コルーチンは呼び出し先が解決するとyield時点から再開 というスタックレス非同期コルーチンが何万も稼働しています 「コ」ルーチンなんだから上位という概念がそもそもない >>87 スタックレス非同期コルーチンは上位ランタイムがCPUコアスレッド数をフルに使って幾万個の軽い非同期コルーチンをスケジューリングすることでCPU性能を使い尽くすことができる 的外れとは? 現在のネットインフラは>>88 の通り動いている現実を認めたくない? スタックフルのコルーチンは、内部で呼び出したコルーチンがyieldしてるかどうか呼び出し元が知っていないといけない つまりカプセル化を破壊する 複数のスレッドが協調して何かするなんてのは、もはやプログラミング言語の領分じゃないんだよなぁ Erlang OTPみたいなアプローチが良いと思う 重要なのはプロトコル 定められた型のメソッドを持つことが重要 関数とオブジェクトなんて、要はどっちも値を入れたら何かを返す箱なのだから、内部構造を考えなければ共通化できるはず 配列も、機能だけ見れば、キーが数値なだけのただのオブジェクト リストやジェネレータなどをイテレートするのも、関数を引数なしで呼び出すようなもの Pythonは__call__()メソッドを実装することでオブジェクトも関数のように振る舞える これは小規模なプログラムでは関数として実装しておき、大規模になったらシームレスに移行できるように Iterable⊂Callable⊃Associative だが、Iterableとしての呼び出し方とAssociativeとしての呼び出し方が異なる x = [1, 2, 3]をIterableとしてCallableだと思う場合、x() = next(x) AssociativeとしてCallableだと思う場合、x(n) = x[n] になる マシン語を知らないと妙なこだわりを言い出すからおもしろいよなw >>101 コンピュータサイエンスにおいて低レイヤーが上位という価値観があるのって、おそらく日本だけだと思うんだけど、どうしてこういう逆行現象が起こるんだろう? 数学だと基礎論は馬鹿にされてるよね 通常の数学やるときに選択公理だとか不完全性定理だとかいってるのは素人だけ そりゃ形式的に「基礎論のほうが原理に近い」というだけならべつに数学を知らなくても分かるからね >>103 マシン語わかる でもコンピューターサイエンスの素養なし って感じのロートルが必至にマウントとろうとしてるだけだろな ふつうは情報学科でも昔ならSICPでやったようなプログラミングの概念や機能に関する講義があるはずで、 ちゃんと勉強している人で実装と機能を分けて考えるということができない人はいないよ 日本で低レイヤーが偉そうな顔してるのは単に、大学等で体系的に学ばずに昔からパソコンに触ってただけのおじさんが言ってるだけ 日本ではプログラマが高等教育が必要な専門技術職として見なされておらず、パソコンしか取り柄のない社会不適合者を低賃金でこきつかう職種だと見なされていたのが原因 >>104 物理学科の自称秀才がやたらと素粒子論専攻したがるのもこれだよなぁ…… 実際に勉強して内容に興味を持てたかどうかではなくて、ラベルで判断してる 20過ぎた大人が将来の進路に関わる選択でそういうことするのは、病的としか言いようがない >>99 ってことは>>31 の方法でも菱形継承問題ふつうに発生するやん Iterable < Callable __call__ = __next__ Associative < Callable __call__ = __getattr__ Array < Iterable, Associative __next__ = piyopiyo __getattr__ = hogehoge arr = Array(blahblahblah) arr() はどっちになるべき? a) 実装継承 b) 多重継承 c) 多段階継承 ができる時点で菱形継承問題は避けられない Rubyは、クラスは(b)を、モジュールは(c)を諦めることでこの問題を回避している Rubyのrefinementsみたいに、このスコープでは型定義を変えるみたいなのがあればまだマシになんのかな たとえば、あるブロック内でだけ(Int, Int)をPoint2Dとみなすとか >>108 ・具象型とインタフェースを分ける 具象型→Int, Str, List<T>など インタフェース→Comparable, Iterableなど ・多重継承したインタフェースで仮想でないメソッドに名前の重複があったらエラーにする とするしかないのでは 型とインタフェースは分けないと、たとえば equal(x: Eq, y: Eq) -> bool とか無意味になる xとyには具体的な型が同じものが入ってほしいのに、Eqインタフェースを持っている型すべてを許容してしまう ほんとうにやりたいのは equal(x: T, y: T) -> bool where T: Eq ということ 型システムと並行性はむずかしいな でも、言語がプログラマに好まれるかどうかってそんなとこにないと思うよ 一時期、高機能なタスクランナーやら単体テストフレームワークやらがたくさん出てきたけど、結局はnpm-scriptsとか言語標準のunittestライブラリに収束した powershellはプログラミング言語としてはbashよりもはるかに高機能だが、誰もあれで書きたいとは思わない >>108 inheritanceを型に対して行うのが間違いな気がする Int x Intを、Point2DとRationalのサブクラスとしたとする Point2DとRationalの両方に + を実装(前者にはベクトルとして、後者には有理数として)したとして、 (1, 2) + (3, 4) はどうなるか? それは結局、今(1, 2), (3, 4)をどの型だとみなしているかによるのであって、型Int x Intの与り知らぬところだ >>109 Mixinで多重継承問題回避できる理屈がよくわからんかったけど、今ようやっと理解できたわ >>113 結局Rustのトレイト境界が正解ということか >>109 Javaの場合は、クラスは(b)を、インタフェースは(a)を諦めている ダイヤモンド継承は、コンパイラが検出するのが正しい気がする 構文で回避するには、>>109 みたく機能を制限するしかない RustとHaskellはしてくれる まあ、でもプログラミング言語が人気になるかどうかは >>114 の言うように、処理系の完璧さではなく、実用のニーズに耐え得ることなんだろうな クロージャとオブジェクトが同等なのはわかったが、それよりもさらに強力な言語機能はないのか? 継続 プログラマが直接触らないものとしては、ガベージコレクションとかレキシカルクロージャとかもそうかもね Eiffel、Delphi、C#なんかは菱形継承してもそれぞれに別名を与えて捌ける機能を持ってるが オブジェクト指向自体が下火なのもあって、最近のやつはそこまで作り込まれてない感 多重継承は名前がぶつかろうがそれ自体に問題は一切ない 抽象型から具象型への継承で実装継承でないならば区別できる限り問題は生じない 本質的な問題は実装継承であること 特にに具象型から具象型への継承が問題 これは多重かどうかに関係なく問題となる そもそもOOPにおける継承ってのは、数学的に破綻してるんだよ >>116 で述べているとおり 型定義にサブタイピングの実装方法が入るわけがない 自然数 is a モノイド と自然にみなす方法は2通りある その方法は自然数の定義とは関係ない クラスが欠陥品なのは同意だが クラスを捨てて型クラスを使えばよい 行列式=1の2次正方行列が、 自分の型はM(2)なのか、GL(2)なのか、SL(2)なのかなんて知らんがな >>127 の問題は型クラスやtraitでも発生するしなあ C++で没になったconcept_mapのようなものを複数切り替えられる機能が求められる x: T where T: K where K: L where ... のように高階の型や fn(x: T) -> Type のようにパラメータに依存する型などがいくらでも作れたらどんなことができるのだろうか >>131 具象型Tに対して抽象型Kの制約を課すところまでは普通として 抽象型Kに対して高階抽象型Lの制約を課すメリットが見つからなかった 代替の方法として高階とせずに 抽象型Kに対して同じレベルの抽象型Lの制約を常に課す宣言ができれば十分にみえる >>60 これを一般化したのが依存型やね これが扱えると数学の証明を型で書ける なんでもラムダ →なんでもオブジェクト →なんでも型 全称量化子は関数で 存在量化子はパターンマッチングで 表せるので、一階述語論理もラムダ式で書けそう 書けるの意味がわからんな 書いてどうする? カリー・ハワード対応とかチューリング完全と関係ある話か? 存在命題とか背理法とか、仮定を仮引数にしたラムダ式を使えば、具体的に証明や値を構成しなくても所望のものを取ってこられるんだな res = hoge()みたいにただ結果返すのと、 hoge((res) => doSomething())みたいに引数に結果入ってくるのと、何が違うんや まず、hogeが同期的でない場合は res = hoge() とは書けない async/awaitのような機構が必要になる またたとえば、エラーの場合は処理を分岐させたいような場合、 hoge( (res) => doSomething(), (err) => handleError()) のように拡張できる ただし、内部でさらに同じようなことをやっているとコールバック地獄になる >>134 依存型のあるHaskellやLean4はとくにオブジェクト指向という感じはしない >>141 純粋関数型で、(クラスベースの)オブジェクト指向をやる意味は皆無だからな 純関数型言語でなくても モダンなプログラミング言語 Go、Rust、Zig、Nim、Julia、Elixirなどは クラスおよびその継承を言語仕様から排除しておりクラスは存在しない それら各々の言語は全く異なる方針を採っている言語だがクラス排除だけは全てが同じ方針である クラスとその継承は悪手であるとプログラミング言語界では結論が出ている だからってduck typingはクラスよりさらに悪いと思うんだ >>144 だからクラスとダックタイピングを採用しない言語が増えているね P∧Qの導入則は、P -> Q -> P∧Q これは(P, Q)ならばP∧Qとも読めるし Pを仮定したとき、QならばP∧Qとも読める 限定継続(reset/shift)の動きが意味わかんない resetで範囲絞ってる分call/ccより実用的には扱いやすいというのは分かるが、ローカルで見たらcall/ccのほうが直感的に思える 実装の都合では? フル機能の継続はそこまでのスタック全部を生かし続けないといけないし分岐できないといけないしで スタック自体をOSの用意してくれるものとは別に自前で構成しないといけないしそうするとABIからなにからつらい 内部で継続渡しに変換してるなら、フルの継続のほうが実装しやすいと思うけど Schemeの場合、dynamic-windとかも実装しなきゃいかんからより複雑だろうけど 継続渡しはスタック消費をクロージャで置き換えてるわけで、スタックを自前で用意してるのと同じようなもんでは 処理が A → B → C → ... とあって A → ... → shift(fn k -> hoge) → B → ... → C → reset とすると k に処理 _ → B → ... → Cが束縛される hogeを実行するとresetまでジャンプする resetの値はhogeの値になる なので、たとえばバックトラックがしたいなら、 戻ってきたい場所にshiftを設置して、 fn () -> k next_valueをスタックに積んで、 クロージャの内部でk current_valueを実行すればいい で、スタックから継続をpopして実行する関数を別に作って 選択肢がなくなったときはそれを呼べばいい カプセル化を破壊するのが前提なので、ものすごく気を遣う call/ccやreset/shiftの型はどうなるの? read.cgi ver 07.5.1 2024/04/28 Walang Kapalit ★ | Donguri System Team 5ちゃんねる