From: https://commandcenter.blogspot.de/2014/01/self-referential-functions-and-design.html
- options.go
- debug.go
- self.go
From: https://commandcenter.blogspot.de/2014/01/self-referential-functions-and-design.html
package main | |
type Foo struct { | |
verbosity int | |
} | |
// That [options.go] is easy and probably good enough for most purposes, but | |
// for the package I'm writing, I want to be able to use the option mechanism | |
// to set temporary values, which means it would be nice if the Option method | |
// could return the previous state. That's easy: just save it in an empty | |
// interface value that is returned by the Option method and the underlying | |
// function type. That value flows through the code: | |
type option func(*Foo) interface{} | |
func Verbosity(v int) option { | |
return func(f *Foo) interface{} { | |
previous := f.verbosity | |
f.verbosity = v | |
return previous | |
} | |
} | |
func (f *Foo) Option(opts ...option) (previous interface{}) { | |
for _, opt := range opts { | |
previous = opt(f) | |
} | |
return previous | |
} | |
// The client can use this the same as before, but if the client also wants to | |
// restore a previous value, all that's needed is to save the return value | |
// from the first call, and then restore it. | |
func main() { | |
foo := &Foo{} | |
prevVerbosity := foo.Option(Verbosity(3)) | |
// foo.DoSomeDebugging() | |
foo.Option(prevVerbosity.(int)) | |
} | |
// The type assertion in the restoring call to Option is clumsy. We can do better | |
// if we push a little harder on our design. |
package main | |
import "fmt" | |
// I've been trying on and off to find a nice way to deal with setting options | |
// in a Go package I am writing. Options on a type, that is. The package is | |
// intricate and there will probably end up being dozens of options. There are | |
// many ways to do this kind of thing, but I wanted one that felt nice to use, | |
// didn't require too much API (or at least not too much for the user to | |
// absorb), and could grow as needed without bloat. | |
// I've tried most of the obvious ways: option structs, lots of methods, | |
// variant constructors, and more, and found them all unsatisfactory. After a | |
// bunch of trial versions over the past year or so, and a lot of | |
// conversations with other Gophers making suggestions, I've finally found one | |
// I like. You might like it too. Or you might not, but either way it does | |
// show an interesting use of self- referential functions. | |
// I hope I have your attention now. | |
// Let's start with a simple version. We'll refine it to get to the final | |
// version. | |
type Foo struct { | |
verbosity int | |
} | |
// First, we define an option `type`. It is a function that takes one | |
// argument, the Foo we are operating on. | |
type option func(*Foo) | |
// The idea is that an option is implemented as a function we call to set the | |
// state of that option. That may seem odd, but there's a method in the | |
// madness. | |
// Given the option type, we next define an Option method on *Foo that applies | |
// the options it's passed by calling them as functions. That method is | |
// defined in the same package, say pkg, in which Foo is defined. | |
// Option sets the options specified. | |
func (f *Foo) Option(opts ...option) { | |
for _, opt := range opts { | |
opt(f) | |
} | |
} | |
// Now to provide an option, we define in pkg a function with the appropriate | |
// name and signature. Let's say we want to control verbosity by setting an | |
// integer value stored in a field of a Foo. We provide the verbosity option | |
// by writing a function with the obvious name and have it return an option, | |
// which means a closure; inside that closure we set the field: | |
func Verbosity(v int) option { | |
return func(f *Foo) { | |
f.verbosity = v | |
} | |
} | |
// Why return a closure instead of just doing the setting? Because we don't | |
// want the user to have to write the closure and we want the Option method to | |
// be nice to use. (Plus there's more to come....) | |
// In the client of the package, we can set this option on a Foo object by | |
// writing: | |
func main() { | |
foo := &Foo{} | |
// foo.Option(pkg.Verbosity(3)) | |
foo.Option(Verbosity(3)) | |
fmt.Printf("%+v\n", foo) | |
// &{verbosity:3} | |
} |
package main | |
import "fmt" | |
type Foo struct { | |
verbosity int | |
} | |
// First, redefine an option to be a function that sets a value and returns | |
// another option to restore the previous value. | |
type option func(f *Foo) option | |
// This self-referential function definition is reminiscent of a state | |
// machine. Here we're using it a little differently: it's a function that | |
// returns its inverse. | |
// Then change the return type (and meaning) of the Option method of *Foo to | |
// option from interface{}: | |
func (f *Foo) Option(opts ...option) (previous option) { | |
for _, opt := range opts { | |
previous = opt(f) | |
} | |
return previous | |
} | |
// The final piece is the implementation of the actual option functions. Their | |
// inner closure must now return an option, not an interface value, and that | |
// means it must return a closure to undo itself. But that's easy: it can just | |
// recur to prepare the closure to undo the original! It looks like this: | |
func Verbosity(v int) option { | |
return func(f *Foo) option { | |
previous := f.verbosity | |
f.verbosity = v | |
return Verbosity(previous) | |
} | |
} | |
// Instead of just returning the old value, it now calls the surrounding | |
// function (Verbosity) to create the undo closure, and returns that closure. | |
func main() { | |
foo := &Foo{} | |
prevVerbosity := foo.Option(Verbosity(3)) | |
fmt.Printf("%+v\n", foo) | |
// foo.DoSomeDebugging() | |
foo.Option(prevVerbosity) | |
fmt.Printf("%+v\n", foo) | |
} | |
// &{verbosity:3} | |
// &{verbosity:0} | |
// Appendix. | |
// And finally we take it up one more level, using Go's defer mechanism to tidy | |
// it all up in the client: | |
// | |
// func DoSomethingVerbosely(foo *Foo, verbosity int) { | |
// // Could combine the next two lines, | |
// // with some loss of readability. | |
// prev := foo.Option(pkg.Verbosity(verbosity)) | |
// defer foo.Option(prev) | |
// // ... do some stuff with foo under high verbosity. | |
// } | |
// | |
// It's worth noting that since the "verbosity" returned is now a closure, not | |
// a verbosity value, the actual previous value is hidden. If you want that | |
// value you need a little more magic, but there's enough magic for now. | |
// | |
// The implementation of all this may seem like overkill but it's actually | |
// just a few lines for each option, and has great generality. Most important, | |
// it's really nice to use from the point of view of the package's client. I'm | |
// finally happy with the design. I'm also happy at the way this uses Go's | |
// closures to achieve its goals with grace. |