Hacker News new | past | comments | ask | show | jobs | submit login
BBS, Lua, Coroutines, Mongrel2: Part 1 (sheddingbikes.com)
62 points by alexkay on Oct 17, 2010 | hide | past | favorite | 33 comments



I ran a BBS in the Baltimore area as a kid. Of course, it was only open after midnight and closed again at 4 am. I used to set my alarm and sneak upstairs to turn off/on the ringers on the other phones. I had about 8 regular callers from all over MD -- this was practically international to a 9 year-old. I ran TriBBS.


Yeah I love BBSes. Seems a lot of other folks like them too so I'm going to continue hacking on this as the official "something more than chat" demo.


Somewhat related to the coroutine vs. FSM issue: http://eli.thegreenplace.net/2009/08/29/co-routines-as-an-al...

[apologize for the plug, but IMHO it's very relevant]


You don't need coroutines to do this - you can use tail calls, if the language provides tail-call optimization. (Python doesn't, but Lua does.) I might post a code sample translating your FSM example later, but I need to go run errands.


That'd be super slick. BTW, you're the guy that wrote sidereal right? I'm using it for the redis action. Very nice.


Yes, and thanks. :)

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.


For the translated AST, see: http://news.ycombinator.com/item?id=1801569


I would be interested in a code sample. I didn't know Lua supported TCO. (I bought PiL, btw, and it helped me tremendously in understanding many programming language implementation issues. Highly recommended.)


This could be done in a couple different ways. My point was that you can use multiple functions with tail calls rather than one big self.state == self.STATE_TAG switch statement. In this case, the input for loop works like a trampoline, so the stack doesn't build up. It could have been done the same way in Python (where self.state IS the "handle next byte" method). That wouldn't be true for all FSMs, though.

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.


I don't have a blog to plug. So, I'll shameless plug my Reddit post regarding coroutines vs. FSMs instead:

http://www.reddit.com/r/programming/comments/9fcna/coroutine...


Enter Lua's coroutines, which are really the only full and complete coroutines.

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


While I'm not current on the newest version of Python (I switched to Lua around py 2.5), Python generators had some functional limitations - most notably, they could only yield from their main body, not from within function calls, IIRC. Those issues struck me as kind of arbitrary, but were due to how Python interacts with the C stack.

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.


There's actually a theory of what makes a complete coroutine, similar to what makes a complete closure. I don't remember all of it off the top of my head, but it's basically:

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.


It's probably helpful to think of coroutines as first-class functions with independent call stacks, which can be suspended and resumed (potentially returning and passing in new arguments). Coroutines are also usable as (one-shot) continuations, and continuations can be used to implement coroutines.

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.


I believe the Coro module for Perl satisfies all of those.

http://search.cpan.org/~mlehmann/Coro-5.23/Coro.pm


Io also gets coroutines right and makes them very easy to use:

* 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).


I see. Python seems to lack the composability requirements of coroutines (3, 4). Thanks.

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.


Python has coroutines if you install greenlet:

http://pypi.python.org/pypi/greenlet

It leads to some really slick networking code.


I ran a BBS out of Stockholm, Sweden on my Atari 1040STFM with a MegaDrive 60MB harddrive which was really loud in my bedroom all day and all night, like a vacum cleaner. Many different people from all over the country dialed my one phone line connection. People downloaded mostly Public Domain applications have music in MOD-format. I met up with some of the people logging in, they were often older than me and could by booze, haha. Just reminiscing....


The problems Zed lists for a co-routine based web app (essentially tying important state to a single server) are also problems for the Scala/Lift framework. Dave Pollak, the author of Lift, claims that maintaining some server-side state solves more problems than it causes but I haven't had a chance to test this myself in a real app.


Does anybody have a good solution for the "Sharing Coroutine State Sucks"-problem? I've thought a very long time about this (in my case, using continuations in FP instead of coroutines in Lua), but I still can't seem to come up with a good answer.

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?


With Stackless Python you can pickle (serialize) tasklets (coroutines). I built a Seaside-style web app in Stackless where each response would get pickled and saved to disk. When a new request with that id comes in you just unpickle it back into a tasklet and resume where you left off. You can do this between processes and machines. I don't see why you couldn't put them in a db but I was just using shared disk. What you're looking for are serializable continuations which some languages can handle.


Very impressive. I'll have to look into that.


You can build a FSM using tail calls. (Lua has TCO.) It's really common to do this in Erlang, too.

It's also possible to stream Lua functions, though it's a bit tricky to do with closures.


You can send functions to Erlang processes for execution, so it shouldn't be that hard to do.

The only problem you end up with then is that Erlang is one extreemly ugly little language.


I ran a Renegade BBS on a machine inside my step-dad's office (Hartford CT area). He was nice enough to let me use his fax number (as he almost never did) with the caveat that he could disconnect people if he needed it.

Very fun times. I used to love the Barren Realms Elite leagues most of all.


Sysop of a Wildcat BBS on a 286. Still remember how excited I was to upgrade to a 386 with an optical drive. The idea that I could store 650MB available for download! If I ever needed more I could just change the disk.

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.


Good timing. I'm working on a MUD server with lua scripting and this coroutine stuff is a much better way to handle input.

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.


Use http://dpaste.de/xtDj/raw/ instead of the given URL.


Thanks, that also has a bug but people who try it should know how to fix it. Basically if it bombs because you don't have json then delete the line of code as it does it correctly further down.


  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.


web-programming with coroutines has been around.

http://cocoon.apache.org/2.1/userdocs/flow/continuations.htm... for one example, but that was still based on prior art.


Figlet "large" font sighted about 20% of the way down. :-)




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: