lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Tue, Sep 23, 2014 at 02:23:12PM -0300, Thiago L. wrote:
> I'm writing an IRC library as the existing ones aren't really good 
> enough (or pure Lua enough) for my purposes. I'm trying to decide on how 
> to handle the messages, but I need some input.
> 
> First method: (I prefer this way as I think it gives the coder more 
> freedom, you can register a single handler and handle any number of 
> events you want, and you can also use this to implement the 2nd method. 
> I could also add priorities to this.)
> 
> irc.addHandler(coroutine.create(function(...)
>     while true do
>         -- something here
>         coroutine.yield()
>     end
> end))
<snip>
> Second method: (this is how LuaIRC does it... not very friendly, 
> especially when you're doing a modular bot...)
> 
> irc.addHandler("eventName", function(...)
> end)
> 
> So, what do you say? Should I go for the 1st or the 2nd method, or 
> should I go for something else entirely?

IMO both of these options are too high-level and make too many assumptions
about the caller. I dislike APIs which make too many assumptions. I'd rather
they make fewer assumptions, but make it trivial for me to extend things.

Whether I'm doing this in Lua, C, or some other language, I typically
implement event emitters using a pull model. That is, the API I expose from
the library is something like:

	local event = foo:get()

	if event.type == "bar" then
		...
	end

Basically like :read or :write, but with objects instead of strings. That
makes the object 100% self-contained, and I don't necessarily have to reason
about how it executes far flung bits of code if I don't want it to.

Of course, many times I'm just going to end up implementing my own function
dispatch table. But many times I won't need to. And other times I really
want to do it myself because I might handle concurrency differently or in a
way that would be incompatible with the way the library dispatches handlers
directly. Fortunately, Lua's coroutines means I can usually invert the
producer-consumer protocol; that is, coroutines often let me convert a push
API into a pull API. But that's needless complexity.

You can also layer a convenience API atop this pull model. The best APIs are
ones that are layered atop each other. First, the user then has a choice of
how to handle things. Secondly, nothing will shake the bugs out of an API
better than using it yourself to build some higher-level API.

Also, to keep things really self-contained I don't let these things do any
I/O or data retrieval internally, either. I manually push the data into the
object (like a :write) and then manually pull it out (like a :read). That
makes it infinitely easier to create abstractions over the object, rather
than relying on a bunch of clunky callback interfaces.