Wandering Thoughts archives

2014-06-07

Some ways to do sleazy duck typing in Go (from a Python perspective)

Normal duck typing in Go is straightforward; you define an interface type (if necessary) and then create some full implementations of it. I can think of a number of ways to enable code reuse in the style of inheritance, none of which I'm going to ramble on about because I've never done this. But sometimes in Python we do what I'll call sleazy duck typing, where we aren't actually a duck but we need to look enough like one to fool some other code. In Go, there are at least two ways to do this.

Let's call the first way incomplete fakery. In incomplete fakery you don't actually implement a working version of the interface; instead you do only as much as you need and then stub out the rest. Usually you hang this around a struct, often a struct embedding some other interface type that provides as much as possible of the functionality you actually need. The following is an example of faking a net.Conn:

type faker struct {
    io.ReadWriter
}

func (f faker) Close() error                     { return nil }
func (f faker) LocalAddr() net.Addr              { return nil }
func (f faker) SetDeadline(time.Time) error      { return nil }
func (f faker) SetReadDeadline(time.Time) error  { return nil }
func (f faker) SetWriteDeadline(time.Time) error { return nil }
func (f faker) RemoteAddr() net.Addr {
    a, _ := net.ResolveTCPAddr("tcp", "127.10.10.100:56789")
    return a
}

func TestSomething(t *testing.T) {
    var outbuf bytes.Buffer
    writer := bufio.NewWriter(&outbuf)
    reader := bufio.NewReader(strings.NewReader(clientInput))
    cxn := &faker{ReadWriter: bufio.NewReadWriter(reader, writer)}

    res := RequiresANetConn(cxn)
    writer.Flush()
    written := outbuf.String()

    ....
}

(This is adopted from the net/smtp tests from the standard Go packages.)

Because much of the implementation is non-functional, incomplete fakery is necessarily specific to the code that you're feeding the resulting duck to; you need to know what it needs and what it doesn't care about. As a result, incomplete fakery is probably most often used in tests (as is the case here, where it's simulating a network connection in a way that lets us easily gather the output written to the 'network').

The second way is what I'll call interposition. In interposition you wrap a real and functional implementation of the interface you need with code that alters its behavior in some way that you find useful. My wrong way to do Go logging entry shows an example of interposition where I put my own code on top of an io.Writer's Write() in order to mutate its behavior to add a prefix to everything written. Interposition takes full advantage of Go's ability to embed interface types in structs and to have the methods on those types transparently shine through the struct (except for anything that you deliberately preempt).

Because it's wrapping a real implementation, in theory interposition works with any code. However, if you've mutated the actual behavior of the real duck too much some code may explode; for example, if you wrote chunks of a line to my interposed io.Writer the resulting output would have the prefix sprayed all over it. Interposition used for sleazy purposes usually has some embedded assumptions about just how the resulting duck is really going to be used.

(Note that not all uses of interposition in Go are sleazy duck typing; it's a situational thing. Composing interfaces this way is one of Go's useful and powerful features and Go makes it much easier and less of a hack than it is in, eg, Python (since I started out by mentioning that language).)

There are probably other ways to do sleazy duck typing in Go. These are just the two that I've figured out and used so far.

programming/GoSleazyDuckTyping written at 00:15:49; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.