Idiomatic multiple-event handler in Go?

1,920 views
Skip to first unread message

Steven Degutis

unread,
Mar 24, 2011, 12:36:16 AM3/24/11
to golan...@googlegroups.com
Hello there!

I'm relatively new to Go, and I'm trying to wrap my head around idiomatic Go patterns. The most recent thing I'm having trouble with is writing an event handler. Now, I can write an event-handler just fine. But I keep relying on my OOP concepts, which feel very un-idiomatic in Go. One of the goals I'm trying to do is avoid just passing my event-handler object to an "interface{}" parameter, because I lose a good chunk of compile-time type checking, and have to rely on runtime type assertions after that point.

With my current implementation, I want to allow a single object to implement anywhere between none or all of the methods of a given interface, "EventHandlerType". That interface contains several methods such as UserDidJoin(channel, username string) and UserDidQuit(username string). But since Go doesn't have "optional methods" in interfaces, I had to work around that by implementing this type with another type "DefaultEventHandler", where each of its methods are no-ops. Then, I just embed DefaultEventHandler into my own handler, and override whatever methods I want to implement. This way, I get to keep compile-time checking, but it's quite ugly and feels very un-idiomatic, almost like faking inheritance.

The only other type-based implementation I can think of, is to have a struct called EventHandlers, which contains a bunch of interface-fields, each one being an interface with only one method.. ie, UserDidJoinHandler interface{ UserDidJoin(channel, username string) } ... this feels a lot more idiomatic, I can then create an EventHandlers object, fill all its relevant fields with my handler, and set it as the event handler. All the compile-time type checking remains in place, and no "fake inheritance" is anywhere to be found. But, the problem with this is that I end up having code like bot.AddEventHandlers(EventHandlers{ myHandler, myHandler, myHandler, myHandler, myHandler, myHandler }) which not only is ugly and tedious, but also slightly seems un-idiomatic.

One final implementation I can think of is just using a switch inside a single HandleEvent(event string, args []string) function, which would handle the event with a switch, but that totally feels like cheating, although I can't place my finger on why.

Anyway, thanks for reading this. If you have any thoughts or helpful suggestions, that would be very welcome and appreciated!

-Steven Degutis

Ian Lance Taylor

unread,
Mar 24, 2011, 1:53:53 AM3/24/11
to Steven Degutis, golan...@googlegroups.com
Steven Degutis <steven....@gmail.com> writes:

> With my current implementation, I want to allow a single object to implement
> anywhere between none or all of the methods of a given interface,
> "EventHandlerType". That interface contains several methods such as
> UserDidJoin(channel, username string) and UserDidQuit(username string). But
> since Go doesn't have "optional methods" in interfaces, I had to work around
> that by implementing this type with another type "DefaultEventHandler",
> where each of its methods are no-ops. Then, I just embed DefaultEventHandler
> into my own handler, and override whatever methods I want to implement. This
> way, I get to keep compile-time checking, but it's quite ugly and feels very
> un-idiomatic, almost like faking inheritance.

I think that's what I do given the constraints you want as I understand
them. You seem to want your objects to all inherit a set of default
methods, so using inheritance seems like the right approach.

Ian

roger peppe

unread,
Mar 24, 2011, 4:19:38 AM3/24/11
to Steven Degutis, golan...@googlegroups.com

you might want to think about avoiding event handling callbacks
entirely and use channels of events. that is - when an event occurs,
you encapsulate the event in some
data structure and send it down a channel.

the "event handler" is then a goroutine with a simple loop that reads from
the channel and processes events as they arrive.
there's no need for an interface{} parameter because all the
context is local to the handler.

whether you have several channels each for a different
kind of event or one channel for all events is a matter of taste.

this architecture has the advantage that several mutually incognizant
event types can integrate easily together, and code for dealing with
events can be written in a very straightforward style (active rather
than passive).

Namegduf

unread,
Mar 24, 2011, 4:48:18 AM3/24/11
to golan...@googlegroups.com

Your first idea sounds like it might be a reasonable way to write a
multiple-event handler; effectively your interface's functions become a
table of function pointers passed to whatever expects it, with no-op
functions provided by the embedded "default" type for "no entry", and
the associated value storing any state used by those handlers.

That said, I think it'd be worth considering a variation on your second
example in which instead of single method interfaces each with a
custom interface type, with the calling code using an implementing type
for each, you just use function values for each member variable, making
it in essence an explicit form of the first idea with empty entries
being nil instead of no-op functions.

It's simpler- eliminates a type per hook, plus a type per hook in every
package adding one, with the only weakness being that if you want to
have multiple instances of those types generated and passed in separate
AddEventHandlers calls, with different state, they have to be closures
closing over that state, so if you want to do that most of the time it
might not be any prettier.


Since you requested any thoughts, my two cents based on your second
example: I wonder whether the constraint you're imposing of a single
multiple-event handler is actually necessary. Any reason why you can't
hook each event separately at whatever point you'd be calling
"AddEventHandlers", passing a function each time, e.g.
bot.HookJoin(joinHandler), bot.HookQuit(quitHandler), where joinHandler
and quitHandler are functions (possibly closures).

I've used that model for hooking event in a Go program and it's been
both simple (no additional event handler types at all), type safe, and
efficient; calling hooks for an event is simply iterating over a slice
of function pointers appended to each time a hook is added and calling
each one, all of which are actual hooks.

If you chose to use channels instead of callback functions (as someone
else suggested) then you could pass a channel to such hook functions
instead of a callback function easily.

If there's a reason you definitely need a multiple event handler, then
disregard this. :P

Mue

unread,
Mar 24, 2011, 5:37:53 AM3/24/11
to golang-nuts
Hi Steven,

just as a small hint: I'm developing a kind of event-driven
architecture as part of my Tideland CGL (http://code.google.com/p/
tideland-cgl/ and http://www.tideland.biz/projects/tideland-cgl). The
idea is a network of 'cells' which contain a cell dehaviour. This cell
behaviour is implementing a defined interface for the event handling
together with some methods needed for the infrastructure. Cells have
in input and an output for events and inputs of one cell can connect
to outputs of other cells.

Currently the development is paused due to my implementation of a
Redis client. But this is almost done, so I'll continue developing
more predefined and configurable cell behaviours. Some are already
included in the package.

mue

On 24 Mrz., 05:36, Steven Degutis <steven.degu...@gmail.com> wrote:

> ...

Steven Degutis

unread,
Mar 24, 2011, 9:32:50 PM3/24/11
to golang-nuts
Thanks everyone for the awesome replies!

So far the approach I think fits this situation best is the channel-
based events approach. Each "event handler" spins off its own
goroutine, which does `for event := <-
bot.RegisterForUserDidJoinEvent() { ... }` and the method
RegisterForUserDidJoinEvent() creates a channel for sending events,
adds it to a private "channelsForUserDidJoinEvents" slice to be sent
to later, and returns it. It's very clean and minimal and type-safe
and everything!

The only drawback is that it's going to require writing a method for
every type of event that are all pretty much identical, with almost no
way of refactoring their functionality out. Granted, it's only 3 lines
of code per event-channel-registration method, just creating a
channel, adding it to a slice, and returning it, but still, that's
definitely something I want to see if I can refactor somehow, if
possible. Any further thoughts on this aren't expected (you've all
been more than helpful) but would still be appreciated.

Thanks and God bless you all real good <3
-Steven


On Mar 24, 4:37 am, Mue <m...@tideland.biz> wrote:
> Hi Steven,
>
> just as a small hint: I'm developing a kind of event-driven
> architecture as part of my Tideland CGL (http://code.google.com/p/
> tideland-cgl/ andhttp://www.tideland.biz/projects/tideland-cgl). The

unread,
Mar 25, 2011, 4:43:04 AM3/25/11
to golang-nuts
On Mar 25, 1:32 am, Steven Degutis <steven.degu...@gmail.com> wrote:
> So far the approach I think fits this situation best is the channel-
> based events approach. Each "event handler" spins off its own
> goroutine, which does `for event := <-
> bot.RegisterForUserDidJoinEvent() { ... }` and the method
> RegisterForUserDidJoinEvent() creates a channel for sending events,
> adds it to a private "channelsForUserDidJoinEvents" slice to be sent
> to later, and returns it. It's very clean and minimal and type-safe
> and everything!

I have some doubts whether this is a good idea. The problem is the
concurrency. It introduces a lot of "new degrees of freedom" into the
code. The bigger the code gets, the worse - almost exponentially
worse. Unfortunately, the Go language is not offering any methods to
the programmer of how to take control over concurrency-induced issues.

You wrote that "it's very clean and minimal and type-safe". That may
be true, but only if the term "type-safe" means "type-safe without any
concern about concurrency issues".

roger peppe

unread,
Mar 25, 2011, 6:09:24 AM3/25/11
to ⚛, golang-nuts
On 25 March 2011 08:43, ⚛ <0xe2.0x...@gmail.com> wrote:
> On Mar 25, 1:32 am, Steven Degutis <steven.degu...@gmail.com> wrote:
>> So far the approach I think fits this situation best is the channel-
>> based events approach. Each "event handler" spins off its own
>> goroutine, which does `for event := <-
>> bot.RegisterForUserDidJoinEvent() { ... }` and the method
>> RegisterForUserDidJoinEvent() creates a channel for sending events,
>> adds it to a private "channelsForUserDidJoinEvents" slice to be sent
>> to later, and returns it. It's very clean and minimal and type-safe
>> and everything!
>
> I have some doubts whether this is a good idea. The problem is the
> concurrency. It introduces a lot of "new degrees of freedom" into the
> code. The bigger the code gets, the worse - almost exponentially
> worse. Unfortunately, the Go language is not offering any methods to
> the programmer of how to take control over concurrency-induced issues.

i believe that this is the way that Go is intended to be used.
channels can make the control of concurrency much more
straightforward.

it is not my experience that this kind of idiom makes the
problem worse as the program gets bigger - as long as
some discipline is observed when connecting components,
it can result in very clean and modular code.

i generally try to live by the golden rule "avoid cycles whenever
possible". as long as you restrict yourself to acyclic topologies,
concurrency issues are minimal.

javier....@gmail.com

unread,
Aug 30, 2013, 2:31:51 PM8/30/13
to golan...@googlegroups.com, steven....@gmail.com
i think its probably a little too late for this, but u can try this approch for your handle

type Handler interface {
    Handle(Event) Event
}

type HandlerFunc func(Event) Event

func (l ListenerFunc) Handle(e Event) Event {
    return l(e)
}

this an interface, with one Method, Handle, and u need to encapsulate the event some how to pass it arround the other type (HandlerFunc func(Event) Event) is there to allow to define a function with that signature and use it as a handler (with that there u can make any function to be of type Handler by just casting it a way, even closeures, it just need to have the same signature)

with that you can just pass your handlers just fine, is the aproach take by the http package for its handlers...

by the way, you dont need to return events.
Reply all
Reply to author
Forward
0 new messages