如何对匿名函数进行类型断言?
How can I make type assertions against an anonymous function?
我正在使用 Gorilla 在 Go 中编写 HTTP 服务。我是 Go 的新手(<1 年经验),但提升速度相当快。
我有一个函数可以用来注册我的处理程序:
func (s *Server) RegisterHandler(path string, handler http.HandlerFunc, methods ...string) {
if len(methods) == 0 {
s.Router.Handle(path, handler).Methods(http.MethodGet)
} else {
s.Router.Handle(path, handler).Methods(methods...)
}
}
我有一些代码将命名函数注册为处理程序:
func (s *Server) RegisterDefaultHandlers() {
s.RegisterHandler("/ping", Ping)
}
func Ping(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
我还有将匿名函数注册为处理程序的单元测试代码:
s.RegisterHandler("/testPath", func(w http.ResponseWriter, r *http.Request) {
// whatever the test calls for
}, http.MethodPost)
这一切都很好 -- 只是建立我的起点。
今天,我发现自己在反对 Go 的类型系统。我正在定义一些自定义处理程序类型,例如:
type UserAwareHandlerFunc func(http.ResponseWriter, *http.Request, models.User)
而且我还引入了一个功能,允许我注册此类处理程序,并将所有处理程序包装在 context.ClearHandler
中。如果这可行,我还将用另一个函数包装所有内容,该函数在我的日志上下文中设置一些内容。我目前拥有的:
func (s *Server) RegisterHandler(path string, handler interface{}, methods ...string) {
wrappedHandler := wrappers.ApplyWrappers(handler)
if len(methods) == 0 {
s.Router.Handle(path, wrappedHandler).Methods(http.MethodGet)
} else {
s.Router.Handle(path, wrappedHandler).Methods(methods...)
}
}
func ApplyWrappers(handler interface{}) http.Handler {
var result http.Handler
if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
result = UserAware(userAwareHandler)
} else if handlerFunc, ok := handler.(http.HandlerFunc); ok {
result = handlerFunc
} else if handlerObj, ok := handler.(http.Handler); ok {
result = handlerObj
} else {
log.Fatalf("handler %+v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
}
// to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
// for all handlers -- see
result = context.ClearHandler(result)
return result
}
func UserAware(handler UserAwareHandlerFunc) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
user := ... // get user from session
handler(w, r, user)
}
}
进行这些更改后,我无法再注册命名或匿名函数; ApplyWrappers
中的类型断言全部失败。我必须声明并定义一个类型化变量,然后将其传入。
命名函数有两种可行的方法:
var Ping http.HandlerFunc = func(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
func Ping2(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
func (s *Server) RegisterDefaultHandlers() {
s.RegisterHandler("/ping", Ping)
var pingHandler2 http.HandlerFunc = Ping2
s.RegisterHandler("/ping2", pingHandler2)
}
对于匿名函数,我可以这样做:
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
...
}
s.RegisterHandler("/testPath", handler, http.MethodPost)
我在这里构建的全部目的是将样板合并到一个地方,使我的许多测试和处理程序尽可能简化。声明类型化变量的需要违背了该目标。所以我的问题是:是否有一些我可以使用的特殊类型的魔法(最好在 RegisterHandler
and/or ApplyWrappers
中)可以恢复将命名 and/or 匿名函数传递给的能力注册处理程序?
编辑:非常感谢您的快速回答。问题已解决:
func ApplyWrappers(handler interface{}) http.Handler {
var result http.Handler
if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
result = UserAware(userAwareHandler)
} else if anonymousFunc, ok := handler.(func(http.ResponseWriter,*http.Request)); ok {
result = http.HandlerFunc(anonymousFunc)
} else if handlerObj, ok := handler.(http.Handler); ok {
result = handlerObj
} else {
log.Fatalf("handler %+v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
}
// to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
// for all handlers -- see
result = context.ClearHandler(result)
return result
}
现在可以用了,但我还有问题:
- 如果我理解正确的话,如果我处理的是接口而不是函数,那么我在这里寻找的“鸭子输入”行为会很好。是什么推动了这种区别?我有理由希望在该语言的未来版本中看到函数的鸭子类型吗?
- 我可以将匿名函数转换为 HandlerFunc。强制转换和类型断言不共享语义对我来说很奇怪。有人可以解释一下吗?
编辑 2:我现在看到了语言规范的一点,它说定义的类型(即具有名称的类型)永远不会 与任何其他类型相同,即使底层类型是相同的,因此永远不会在类型断言中工作(除非它是一个接口)。所以现在我想知道:
- 为什么接口和其他类型之间的语义不同?
- 为什么命名类型和未命名类型的语义不同?
我发现两者都不直观且不方便。我一个人在这里吗?我想知道是否有人知道为什么在设计语言时做出这些决定(也许在不膨胀编译器或其输出等的情况下实现这些决定特别困难),以及是否有人知道解决这两种情况的计划。
http.HandlerFunc
是特定类型。 Ping
函数不是该类型,尽管它 可以 转换为该类型,因为它们具有相同的基础类型。
如果handler
是匿名函数那么你需要在类型断言中使用匿名函数:
f, ok := handler.(func(http.ResponseWriter,*http.Request))
https://golang.org/ref/spec#Type_assertions
- if
T
is not an interface type, x.(T)
asserts that the dynamic type of x
is identical to the type T
- If
T
is an interface type, x.(T)
asserts that the dynamic type of x
implements the interface T
.
我正在使用 Gorilla 在 Go 中编写 HTTP 服务。我是 Go 的新手(<1 年经验),但提升速度相当快。
我有一个函数可以用来注册我的处理程序:
func (s *Server) RegisterHandler(path string, handler http.HandlerFunc, methods ...string) {
if len(methods) == 0 {
s.Router.Handle(path, handler).Methods(http.MethodGet)
} else {
s.Router.Handle(path, handler).Methods(methods...)
}
}
我有一些代码将命名函数注册为处理程序:
func (s *Server) RegisterDefaultHandlers() {
s.RegisterHandler("/ping", Ping)
}
func Ping(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
我还有将匿名函数注册为处理程序的单元测试代码:
s.RegisterHandler("/testPath", func(w http.ResponseWriter, r *http.Request) {
// whatever the test calls for
}, http.MethodPost)
这一切都很好 -- 只是建立我的起点。
今天,我发现自己在反对 Go 的类型系统。我正在定义一些自定义处理程序类型,例如:
type UserAwareHandlerFunc func(http.ResponseWriter, *http.Request, models.User)
而且我还引入了一个功能,允许我注册此类处理程序,并将所有处理程序包装在 context.ClearHandler
中。如果这可行,我还将用另一个函数包装所有内容,该函数在我的日志上下文中设置一些内容。我目前拥有的:
func (s *Server) RegisterHandler(path string, handler interface{}, methods ...string) {
wrappedHandler := wrappers.ApplyWrappers(handler)
if len(methods) == 0 {
s.Router.Handle(path, wrappedHandler).Methods(http.MethodGet)
} else {
s.Router.Handle(path, wrappedHandler).Methods(methods...)
}
}
func ApplyWrappers(handler interface{}) http.Handler {
var result http.Handler
if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
result = UserAware(userAwareHandler)
} else if handlerFunc, ok := handler.(http.HandlerFunc); ok {
result = handlerFunc
} else if handlerObj, ok := handler.(http.Handler); ok {
result = handlerObj
} else {
log.Fatalf("handler %+v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
}
// to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
// for all handlers -- see
result = context.ClearHandler(result)
return result
}
func UserAware(handler UserAwareHandlerFunc) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
user := ... // get user from session
handler(w, r, user)
}
}
进行这些更改后,我无法再注册命名或匿名函数; ApplyWrappers
中的类型断言全部失败。我必须声明并定义一个类型化变量,然后将其传入。
命名函数有两种可行的方法:
var Ping http.HandlerFunc = func(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
func Ping2(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
func (s *Server) RegisterDefaultHandlers() {
s.RegisterHandler("/ping", Ping)
var pingHandler2 http.HandlerFunc = Ping2
s.RegisterHandler("/ping2", pingHandler2)
}
对于匿名函数,我可以这样做:
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
...
}
s.RegisterHandler("/testPath", handler, http.MethodPost)
我在这里构建的全部目的是将样板合并到一个地方,使我的许多测试和处理程序尽可能简化。声明类型化变量的需要违背了该目标。所以我的问题是:是否有一些我可以使用的特殊类型的魔法(最好在 RegisterHandler
and/or ApplyWrappers
中)可以恢复将命名 and/or 匿名函数传递给的能力注册处理程序?
编辑:非常感谢您的快速回答。问题已解决:
func ApplyWrappers(handler interface{}) http.Handler {
var result http.Handler
if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
result = UserAware(userAwareHandler)
} else if anonymousFunc, ok := handler.(func(http.ResponseWriter,*http.Request)); ok {
result = http.HandlerFunc(anonymousFunc)
} else if handlerObj, ok := handler.(http.Handler); ok {
result = handlerObj
} else {
log.Fatalf("handler %+v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
}
// to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
// for all handlers -- see
result = context.ClearHandler(result)
return result
}
现在可以用了,但我还有问题:
- 如果我理解正确的话,如果我处理的是接口而不是函数,那么我在这里寻找的“鸭子输入”行为会很好。是什么推动了这种区别?我有理由希望在该语言的未来版本中看到函数的鸭子类型吗?
- 我可以将匿名函数转换为 HandlerFunc。强制转换和类型断言不共享语义对我来说很奇怪。有人可以解释一下吗?
编辑 2:我现在看到了语言规范的一点,它说定义的类型(即具有名称的类型)永远不会 与任何其他类型相同,即使底层类型是相同的,因此永远不会在类型断言中工作(除非它是一个接口)。所以现在我想知道:
- 为什么接口和其他类型之间的语义不同?
- 为什么命名类型和未命名类型的语义不同?
我发现两者都不直观且不方便。我一个人在这里吗?我想知道是否有人知道为什么在设计语言时做出这些决定(也许在不膨胀编译器或其输出等的情况下实现这些决定特别困难),以及是否有人知道解决这两种情况的计划。
http.HandlerFunc
是特定类型。 Ping
函数不是该类型,尽管它 可以 转换为该类型,因为它们具有相同的基础类型。
如果handler
是匿名函数那么你需要在类型断言中使用匿名函数:
f, ok := handler.(func(http.ResponseWriter,*http.Request))
https://golang.org/ref/spec#Type_assertions
- if
T
is not an interface type,x.(T)
asserts that the dynamic type ofx
is identical to the typeT
- If
T
is an interface type,x.(T)
asserts that the dynamic type ofx
implements the interfaceT
.