Select functions for Go

In this thread, Rowan Davies wrote:

There’s no STM implementation for Go, as far as I know. STM doesn’t fit so well with message passing channels, which Go adopts as another quite good alternative to locks. When used well they can avoid the compositionality issues with locks, and have less of a performance cost compared to STM – but STM arguably scales better to complicated situations in terms of the ease of avoiding deadlocks and the like.

STM isn’t as expressive as channels – you can’t build channels within STM (although you can layer them on top of it, which then loses some of the compositionality benefits)

Channels have their own compositionality issues – if you want to be able to alt on something, you need to have access to the raw channel, but the interface might require other things to happen after the channel operation – these must be exposed by the interface, which is undesirable.

For example, in some example code I wrote in a previous post, there’s some code to read a value:

func (r *Receiver) Read() interface{} {
	b := <-r.C
	v := b.v
	r.C <- b
	r.C = b.c
	return v
}

It would be nice if we could have this Read as part of a select statement. Currently, the only way to do this is to make the channel publicly readable, and have a function that the user must remember to call with the value read from the channel:

func (r *Receiver) DoneRead(b broadcast) interface{} {
	v := b.v
	r.C <- b
	r.C = b.c
	return v
}

select {
case b := <-r.C:
    v := r.DoneRead(b)
    ...
....
}

This is error-prone – and more importantly, it breaks encapsulation by exposing the internal-only “broadcast” type.

For a nice way around these problems, I’ve been thinking that something like the select functions provided by the XC language might work well in Go.

A select function would be similar to a normal function except that its top level contains arms of a select statement:

selectfunc read(r *Receiver) interface{} {
    	case b := <-r.C:
	    v := b.v
	    r.C <- b
	    r.C = b.c
	    return v
}

Then you could do:

select {
case v := read(&r):
    .... do something with v
}

i.e. a select function can be used in place of a channel operation in any select arm. Using the select function in an expression would just block as normal.

Select functions seem enough like normal functions that one might ask whether they could be methods too. Given the current syntax. which doesn’t use the func keyword inside interface declarations, I’d say no. And they’re not strictly speaking necessary either. Something not too far from the original interface can be obtained by returning a selectfunc as a closure:

func (r *Receiver) Reader() (selectfunc () interface{}) {
 	return selectfunc() interface{} {
    		case b := <-r.C:
			v := b.v
			r.C <- b
			r.C = b.c
			return v
	}
}

Then you’d do:

select {
case v := r.Reader()() {
	...
}

Not entirely ideal, but quite feasible.

I think there are quite a few benefits to be gained from implementing something like this. And the implementation should be reasonably straightforward – the main implication, as far as I can see, is that the number of arms in an alt statement would not always be statically determinable.

What do people think?

Advertisements

5 Responses to “Select functions for Go”

  1. damjan Says:

    wouldn’t it be much simpler just to use a new go-routine?

  2. rogpeppe Says:

    how would you do that without introducing buffering into the channel?

  3. Steven Says:

    I’m fully aware this is over a year old.

    I had a similar idea quite a while ago, except for reusing channels, ie:

    ch_internal := make(chan int)
    ch := chan (x int) {
    case ch_internal <- x*2:
    case x = <-ch_internal:
    }

    This would make ch a channel where everything passed through it would be multiplied by two (trivial demonstrative example). Of course, this raises the issue of representation: could you represent ch and ch_internal with the same type without introducing a major performance hit on normal channels? Your approach makes that easier by making it a special kind of type, but looses out on generality.

    I'd be interested to hear what happened to this idea in the past year. Was it resoundingly rejected, or has it just sat on the back burner?

  4. Daniel Buckmaster Says:

    For the benefit of future visitors to this post, I’d like to mention that it’s entirely possible to build channels on STM – for example, Haskell’s TChan https://hackage.haskell.org/package/stm-2.1.1.2/docs/Control-Concurrent-STM-TChan.html

  5. rogpeppe Says:

    Daniel: I believe that it’s actually not possible to implement synchronous, composable channels on top of STM. Here’s a challenge: try to come up with an implementation of select using STM, allowing arbitrary combinations of send and receive using unbuffered channels.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: