call/cc in Lua

http://stackoverflow.com/questions/2827620/call-cc-in-lua-possible
であったネタ。


前提知識(?)
[1]Coroutines in Lua
http://www.inf.puc-rio.br/~roberto/docs/corosblp.pdf

[2]Revisiting Coroutines
http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf

[3]Some Proofs about Coroutines
http://www.dbd.puc-rio.br/depto_informatica/08_04_ierusalimschy.pdf


[1]~[3]で言われてるのは、
asymmetric coroutine(Luaのcoroutine,semi-coroutine)=symmetric coroutine=one-shot continuation
は全部等価とかいう話。

あと、"one-shot delimited continuation"とも一緒だよみたいなことが書いてあるけど、実際に使ってるのは、subcontinuationとかいうよく知らない代物。論文では、spawn/controllerとか使ってるけど、これはprompt/controlと同じっぽい?が、これらが、shift/resetと同等なのかとか、知らない。


call/1ccは実装してるScheme処理系がChezSchemeしかないし、使ってるコードとかも全然ないので、なんかよく分からないけど、

(call/1cc (lambda (k) (k 0)))

などはOKだけど、

(define cont #f)
(begin (display "hoge")
       (call/1cc (lambda(c) (set! cont c)))
       (display "fuga")
       (display "piyo"))
(cont)

とかやると、Exception: attempt to invoke shot one-shot continuationといわれる。Luaによるcall/1ccの実装は[2]に書かれてる。


で、じゃあcall/ccと、こいつらの差は何?みたいな疑問があって、coroutineの複製が作れれば、call/cc作れるんじゃねという話。

coroutineの複製をできるようにしたというLua処理系のpatchが
http://lua-users.org/lists/lua-l/2006-01/msg00652.html
らへんにあって、これを使って作ったというcall/ccが
https://github.com/torus/lua-call-cc/blob/master/callcc.lua


複製できるというのは、

local co = coroutine.create(function () for i=1,100 do
                                          print(i)
                                          coroutine.yield(i) end end)
coroutine.resume(co)
coroutine.resume(co)

local co2 = coroutine.clone(co)

coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co2)
coroutine.resume(co2)

とか実行すると、1,2,3,4,3,4が返ってくるという意味。

pythonのitertools.teeは似たようなことができるけど、あれは結果をキャッシュしているだけらしいので、

import itertools
x = 0
def gen():
   for i in xrange(1,100):
     yield (i+x)

co = gen()
co.next()  #yield 1
co.next()  #yield 2
co,co2 = itertools.tee(co)
co.next()  #yield 3
co.next()  #yield 4
x=10
co2.next() #!?
co2.next() #!?

とかいうケースで困る。


で、以下のようなコードが動く。

require "callcc"

function add(x,y) return x+y end

--(display (call/cc (lambda (c) (+ 2 (c 3)))))
function test()
  local r = callcc (function (cont) add(2,cont(3)) end)
  print(r)
end

--(display (+ 2 (call/cc (lambda (k) (k (k 0))))))
function test2()
  local r = callcc(function (k) k(k(3)) end)
  print (2+(r))
end


--(call/cc (lambda (c) ((c 0) (c 1))))
function test3()
  local r = callcc(function(k) k(0)(k(1)) end)
  print(r)
end


callcc_run(test)
callcc_run(test2)
callcc_run(test3)

callcc_runとかどうなの?っていうのと、それを除いてもほんとにこれがcall/ccと等価って言ってよいのか確信がない。実際のとこ
http://d.hatena.ne.jp/m-a-o/20060915#p3
にあるような
(let ((f (call/cc call/cc))) (f (lambda (x) 1)))
というコードは動かないっぽい。どこで死んでるか真面目に調べてはいないけど。


callcc_runをなくすには、VMの実体であるlua_Stateをthreadにしてやれば、ほんとのcurrent continuationを取ることができて、それで同じようなことをしてやればよい気がする(けど、それが実際に動くのかは試してない)。そういうのなしには、callcc_runを消すことは多分できない


実用的な観点からすると、もし本当にcall/ccがcoroutine+cloneと等価なら、後者のほうがずっと標準的な頭脳の人類に優しいsemanticsになる。等価でないなら(後者のほうが強力ということはないだろうから)call/ccに届くには、何が足りないのか知りたい。ていうか、call/ccをprimitiveにしようとか、最初に思いついた人は、どうかしてる


ちなみに処理系を改変するのは気分的に避けたかったので、Luaのソースから、lstate.hだけ持ってきて、以下のようなコードを書いてテストした

#include <stdio.h>
#ifdef  __cplusplus
extern "C"{
#endif
  #include "lua.h"
  #include "lauxlib.h"
  #include "lualib.h"
  //illegal
  #include "lstate.h"
#ifdef  __cplusplus
}
#endif


int clonethread(lua_State *L){
   lua_State *src,*dst;
   if(!lua_isthread(L,1)){
      luaL_argerror(L , 1 , "coroutine expected");
   }
   src = lua_tothread(L , 1);
   lua_lock(L);
   if(!(src->status==LUA_YIELD || (src->status==0 && src->ci==src->base_ci))){
      luaL_error(L , "attempt to clone uncloneable coroutine");
   }
   lua_unlock(L);

   dst = lua_newthread(L);
   lua_lock(dst);
   luaD_reallocstack(dst , src->stacksize - EXTRA_STACK - 1);
   luaD_reallocCI(dst , src->size_ci);

   /* Copy stack slots */
   {
      TValue *f,*t;
      for(f = src->stack,t=dst->stack ; f < src->top ; f++,t++)
         setobj2s(dst , t, f);
      dst->top = t;
      for(; f < src->ci->top ; f++,t++)
         setnilvalue(t);
      dst->base = (src->base - src->stack) + dst->stack;
   }


   /* Copy stack slots */
   {
      TValue *f,*t;
      for(f = src->stack,t=dst->stack ; f < src->top ; f++,t++)
         setobj2s(dst , t, f);
      dst->top = t;
      for(; f < src->ci->top ; f++,t++)
         setnilvalue(t);
      dst->base = (src->base - src->stack) + dst->stack;
   }

   /* Copy Frames */
   {
      CallInfo *f,*t;
      for(f = src->base_ci,t=dst->base_ci ; f <= src->ci ; f++,t++){
         t->base = (f->base - src->stack) + dst->stack;
         t->func = (f->func - src->stack) + dst->stack;
         t->top = (f->top - src->stack) + dst->stack;
         t->nresults = f->nresults;
         t->savedpc = f->savedpc;
         t->tailcalls = f->tailcalls;
      }
      dst->ci = (src->ci - src->base_ci) + dst->base_ci;
   }

   /* Copy misc fields. Hooks are deliberately not copied. */
   dst->status = src->status;
   dst->savedpc = src->savedpc;
   lua_unlock(dst);
   return 1;
}


static const struct luaL_Reg _coroutineLib [] = {
  {"clone" , clonethread},
  {NULL,NULL}
};




int main(int argc, char **argv){
  if(argc==1)return 0;
  lua_State *L = luaL_newstate();
  luaL_openlibs(L);
  luaL_register(L , "coroutine" , _coroutineLib);
  luaL_dofile(L , argv[1]);
  lua_close(L);
  return 0;
}