Closed liamylian closed 4 years ago
You can do something similar by using higher order functions or simply pointers. For example:
package main
import (
"fmt"
"net/http"
)
// `Default` sets default value to a int variable
func Default(intVar *int, value int) {
if intVar == nil || *intVar == 0 {
intVar = &value
}
}
func getBookHandler(w http.ResponseWriter, r *http.Request) {
}
// Route wraps a route into a handler function
func Route(handler http.HandlerFunc, method, uri string) http.HandlerFunc {
return func (w http.ResponseWriter, r *http.Request) {
http.Handle(uri, handler)
}
}
var getBook = Route(http.HandlerFunc(getBookHandler), http.MethodGet, "/books/:id")
func main() {
var httpPort int
Default(&httpPort, 80)
fmt.Printf("httpPort %d", httpPort)
http.ListenAndServe(fmt.Sprintf(":%d", httpPort), getBook)
}
https://play.golang.org/p/dsolvSezkqf (It does give an error since http doesn't work on the playground.) . IMO; decorators are a misfeature of Pyton, in Go we generally don't need such a confusing way to call a function before another though magic syntax.
Yep, the previous example can be achieved by higher order functions. But in the case of auto dependency injection, you have to do it manually if without decorator.
type BookRepository interface {
GetBook(id int) (*Book, error)
CreateBook(book *Book) error
}
type bookService struct {
@di.Inject // `di.Inject(bookRepository)` will be called right after `bookService` instance is created
bookRepository BookRepository
}
func (s *bookService) CreateBook(title, author string) error {
book := NewBook(title, author)
return s.boookRepository.CreateBook(book)
}
func main() {
bookRepository := NewBookRepository()
di.Provide(bookRepository)
bookService := new(bookService) // `di.Inject(bookService.bookRepository)`will be called right after `new`, so bookService.bookRepository is not nil here now.
bookService.CreateBook("Jane Eyre", "Charlotte Bronte") // won't panic
}
// package di
var container map[reflect.Type]interface{}
func Provide(obj interface{}) {
typ := reflect.TypeOf(obj)
container[typ] = obj
}
// decorator `Inject` injects a previous provided instance to obj, which might be nil before
func (obj interface{}) @Inject() {
typ := reflect.TypeOf(obj)
obj = container[typ]
}
As far as this feature is concerned, maybe there is a balance between confusing, beauty, productivity, etc.
Like @beoran , I can't see the point of @Default
at all. There's no reason to add a new syntax to do something that we can already do.
I don't know what @di.Inject
is supposed to mean.
@ianlancetaylor I've updated the previous comment with the code of package di
. The point of @Default
is a simple demo, too simple to have another point. May be @Env
will look a little more convenient, but it's still not complex enough to show the power of decorator.
// Decorator `@Env` sets value to a int variable from System Environment
func (intVar *int) @Env(name string, defaultValue int) {
if intVar != nil && *intVar != 0 {
return
}
val, ok := os.LookUpEnv(name)
if !ok {
*intVar = defaultValue
return
}
intVal, err := strconv.Atoi(val)
if err != nil {
*intVar = defaultValue
return
}
*intVar = intVal
}
type Config struct {
@Env(/*name=*/ "HTTP_PORT", /*defaultValue=*/ 80) // `httpPort.Env("HTTP_PORT", 80)` will be called right after a new instance of `Config` is created.
httpPort int
}{}
var config Config // config.httpPort now equals to 80, because of decorator `@Env` is executed right after variable `config` initialization.
Of course, things can be done without decorators. But the decorator syntax allows us to more conveniently alter variables, functions, etc.
Refers:
Go favors an explicit programming style. If you write out what the program does, everybody reading the program understands what happens. It's true that this leads to more verbose code than in some other programming languages. Go considers the benefit in readability to be worth the extra code.
If we take that as a given, I have no idea what benefit Go gets from adding decorators. As far as I can tell, a decorator is a way to concisely invoke a function. Go already has ways to invoke a function. In your Config
example with an @Env
decorator, the advantage of the decorator is that it is concise, and it applies to every instance of Config
. The disadvantage is that when somebody in some other package far away writes var c pkg.Config
, the value of the httpPort
field is unexpectedly set. If they write c := pkg.NewConfig()
, then it is clear that some fields may be changed.
There are ways to do dependency injection in Go as well, either using reflection, or using code generation, such as this tool/library: https://github.com/elliotchance/dingo In Go "less is exponentially more".
For language change proposals, please fill out the template at https://go.googlesource.com/proposal/+/bd3ac287ccbebb2d12a386f1f1447876dd74b54d/go2-language-changes.md .
When you are done, please reply to the issue with @gopherbot
please
remove
label
WaitingForInfo
.
Thanks!
Proposal: Go 2: function decorator support
After above discussion, maybe only function decorator is a simple and useful sugar. Other decorators, like variable decorator is not so predictable that disobey GoLang's design principle.
Would you consider yourself a novice, intermediate, or experienced Go programmer? Experienced
What other languages do you have experience with? PHP, Python, JAVA
Would this change make Go easier or harder to learn, and why? Not much, it's just a simple decorator of function.
Has this idea, or one like it, been proposed before? If so, how does this proposal differ? Not found.
Who does this proposal help, and why? Framework developers and developers using framework. Like SprintBoot annotations, decorator is a very concise way to add extra behavior to a given function.
Is this change backward compatible? Nope
Show example code before and after the change.
Before:
package main
import (
"fmt"
"log"
"time"
)
type Handler func(string) error
func Recover(h Handler) Handler {
return func(arg string) error {
defer func() {
if r := recover(); r != nil {
log.Printf("recoved from: %v", r)
}
}()
return h(arg)
}
}
func Async(h Handler) Handler {
return func(arg string) error {
go h(arg)
return nil
}
}
func Log(h Handler) Handler {
return func(arg string) error {
err := h(arg)
log.Printf("called f with %s, returns %v", arg, err)
return err
}
}
func hello(name string) error {
fmt.Printf("Hello, %s\n", name)
panic("Ouch")
}
var Hello = Async(Log(Recover(hello)))
func main() {
Hello("Beoran")
time.Sleep(time.Second)
}
After:
package main
import (
"fmt"
"log"
"time"
)
type Handler func(string) error
func (h *Handler) @Recover() {
wrapped := func(arg string) error {
defer func() {
if r := recover(); r != nil {
log.Printf("recoved from: %v", r)
}
}()
return h(arg)
}
*h = wrapped
}
func (h *Handler) @Async() {
wrapped := func(arg string) error {
go h(arg)
return nil
}
*h = wrapped
}
func (h *Handler) @Log() {
wrapped := func(arg string) error {
err := h(arg)
log.Printf("called f with %s, returns %v", arg, err)
return err
}
*h = wrapped
}
@Async
@Log
@Recover
func Hello(name string) error {
fmt.Printf("Hello, %s\n", name)
panic("Ouch")
}
func main() {
Hello("Beoran")
time.Sleep(time.Second)
}
What is the cost of this proposal? How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected? No. What is the compile time cost? Barely none. What is the run time cost? Barely none.
Can you describe a possible implementation?
Trigger decorators
when package initialization.
Do you have a prototype? (This is not required.) No
How would the language spec change? Not sure.
Orthogonality: how does this change interact or overlap with existing features? None.
Is the goal of this change a performance improvement? Nope.
Does this affect error handling? Nope.
@gopherbot please remove label WaitingForInfo
Thanks for filling out the template.
This proposal introduces a new way to do something that the language can already do. Perhaps if we had used this style initially this would be a good fit. But we have years of code that does not use this new approach. This new approach does not seem to provide enough advantage over the existing approach to warrant inclusion.
Also, there is little support for this based on emoji voting.
For these reasons this is a likely decline. Leaving open for four weeks for final comments.
No further comments.
Decorator may make writing GoLang projects more efficient. I hope GoLang support some kind of decorator.