BBS, Lua, Coroutines, Mongrel2: Part 1(sheddingbikes.com) |
BBS, Lua, Coroutines, Mongrel2: Part 1(sheddingbikes.com) |
[apologize for the plug, but IMHO it's very relevant]
http://www.reddit.com/r/programming/comments/9fcna/coroutine...
I'm curious, could someone explain this statement? I was under the impression that Python had coroutines as well. Syntactically they are a bit uglier (yield statements everywhere), but functionally similar: http://www.python.org/dev/peps/pep-0342/
(Admittedly, I don't know a lot about coroutines, so I could be way off here.)
In code: http://gist.github.com/630921
Lua's coroutines don't have those restrictions. I think Stackless Python doesn't have them either (though I haven't used it). Lua is also "stackless" in that sense. Also, Lua's coroutine.yield is a library function, not a keyword.
I'm not convinced coroutines/continuations are a good fit for managing web state, but they do make a lot of other control flow situations easier to manage - "who has the main loop" problems are a non-issue, since they can have an independent main loop.
1. coroutines act as pipes that can send (yield) and receive values.
2. coroutines are first class citizens like closures so they can be passed around as values, referenced, garbage collected, etc.
3. they can yield from any point in the stack, so you can use them inside functions deeply nested without any caller N frames up from knowing about it.
4. one side of a coroutine doesn't have to know about the other side, just like functions.
5. you can inspect them to find out if they are running, suspended, etc.
I'm probably missing some things, but the gist is there's very few languages that do all of this. Lua's the only one that really gets them right and makes it easy, with probably Lisp continuations being next. I'm sure there's other languages but I know Python, Ruby, and Java really get these wrong.
I'd also say that Erlang adds one very very sexy addendum to this in that you can take any process and send it over the wire, store it, recover it, etc.
There's a great paper about coroutines ("Revisiting Coroutines", http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.58.4...) co-written by one of the primary Lua authors.
FWIW, you can also stream Lua functions (with string.dump), though it takes a bit of extra trouble to stream functions with nonlocal values ("upvalues"/closures). I don't know if it's possible to stream Lua coroutines, though. As with most things, Erlang's immutability makes streaming them much easier.
* http://www.iolanguage.com/scm/io/docs/IoGuide.html#Concurren...
* http://www.iolanguage.com/scm/io/docs/reference/Core/Core/Co...
Probably no surprise though because Lua was one of the influences behind Io design/implementation (full list of influencers from http://www.iolanguage.com/):
* Smalltalk (all values are objects, all messages are dynamic),
* Self (prototype-based),
* NewtonScript (differential inheritance),
* Act1 (actors and futures for concurrency),
* LISP (code is a runtime inspectable/modifiable tree)
* and Lua (small, embeddable).It looks as if the only way to compose coroutines in Python (say, using g inside f) would be to call `yield g.next()` every time you want to access g.next() inside f. It works, but definitely not as pretty as the real thing.
http://pypi.python.org/pypi/greenlet
It leads to some really slick networking code.
I guess one way to deal with it is by mechanically transforming the co-routines into a FSM. This is very similar to defunctionalization. However, I don't know of a language that does this well. Any suggestions?
It's also possible to stream Lua functions, though it's a bit tricky to do with closures.
The only problem you end up with then is that Erlang is one extreemly ugly little language.
Very fun times. I used to love the Barren Realms Elite leagues most of all.
Missed many nights of homework working on that thing. Making contests for LoRD or Tradewars. Zed described the death of BBSs very accurately. It went from THE thing to do, to lights out almost overnight.
Eventually I'm going to stick ZMQ-based RPC onto it as well, so other services (M2?) can interrogate the server for things like player lists.
Connecting to mongrel2.org:80
Traceback (most recent call last):
File "client.py", line 36, in <module>
post_msg("connect")
File "client.py", line 28, in post_msg
msg = '@bbs %s\x00' % (json.dumps({'type': 'msg',
'msg': data}))
AttributeError: 'module' object has no attribute 'dumps'
I think it's to do with my python 2.5.http://cocoon.apache.org/2.1/userdocs/flow/continuations.htm... for one example, but that was still based on prior art.
Have you seen my library (http://github.com/silentbicycle/tamale) for doing Erlang-style pattern matching in Lua, BTW? I still need to document it (I'm working on briefly explaining why pattern matching matters to people who have never used it), but in the mean time, a glance at the README and the test suite should suffice.
In something that take input one piece at a time, it'd probably still be simplest to use a coroutine. Also, everything here could be made private except new, if it mattered. I tried to make a straightforword translation of the first Python sample. It looks rather like a recursive descent parser.
local header, footer, dle = '\97', '\98', '\253'
function wait_header(s, byte)
if byte == header then s.frame = {}; return in_msg end
return wait_header
end
function in_msg(s, byte)
if byte == dle then
return after_dle
elseif byte == footer then
return table.concat(s.frame)
else
s.frame[#s.frame+1] = byte --append to frame buffer
return in_msg
end
end
function after_dle(s, byte)
s.frame[#s.frame+1] = s.adf(byte)
return in_msg
end
function new(after_dle_func)
local state, func = { adf = after_dle_func, frame = {} }, wait_header
return function(byte)
func = func(state, byte)
if type(func) == "string" then return func end
end
end
-- test --
foo = new(function(x) print "(in after_dle_func)"; return x:upper() end)
-- this is like "for /./ in string do ..."
for b in string.gmatch("foo\97frame contents\253dle\98end", ".") do
local res = foo(b)
if res then print("GOT FRAME: ", res); break end
end
> dofile("/tmp/lua-6043PeX")
(in after_dle_func)
GOT FRAME: frame contentsDle
Also, Shriram Krishnamurthi's "The Swine Before Perl" (http://www.cs.brown.edu/~sk/Publications/Talks/SwineBeforePe...) also has a good example of using tail calls to write FSMs in Scheme.