如何对匿名函数进行类型断言?

huangapple go评论76阅读模式
英文:

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) {
    // 测试所需的任何操作
}, 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))
	}

	// 为了避免内存泄漏,确保在请求生命周期结束时清除所有请求数据
	// 适用于所有处理程序--参见https://stackoverflow.com/a/48203334
	result = context.ClearHandler(result)
	return result
}

func UserAware(handler UserAwareHandlerFunc) http.Handler {
    return func(w http.ResponseWriter, r *http.Request) {
        user := ... // 从会话中获取用户
        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和/或ApplyWrappers中),以恢复将命名和/或匿名函数传递给RegisterHandler的能力?

编辑:非常感谢快速回答。问题解决了:

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))
	}

	// 为了避免内存泄漏,确保在请求生命周期结束时清除所有请求数据
	// 适用于所有处理程序--参见https://stackoverflow.com/a/48203334
	result = context.ClearHandler(result)
	return result
}

现在它可以工作了,但我仍然有一些问题:

  1. 如果我理解正确,我在这里寻找的“鸭子类型”行为如果我处理的是接口而不是函数,那么就没问题了。是什么驱使了这种区别?在未来的语言版本中,我能否合理地希望看到函数的鸭子类型?
  2. 我可以将匿名函数强制转换为HandlerFunc。对我来说,强制转换和类型断言的语义不同有点奇怪。有人能解释一下吗?

编辑2:我现在已经看到了语言规范中的一部分,它说定义的类型(即具有名称的类型)永远不会与任何其他类型相同,即使底层类型相同,也不会在类型断言中起作用(除非它是一个接口)。所以现在我想知道:

  1. 为什么接口和其他类型之间的语义不同?
  2. 为什么命名和未命名类型的语义不同?

我觉得这两者都不直观和不方便。我是唯一一个这样认为的吗?我想知道在设计语言时为什么做出这些决定(也许在不膨胀编译器或其输出的情况下实现它特别困难等),以及是否有计划解决这两种情况。

英文:

I'm writing an HTTP service in Go using Gorilla. I'm newish to Go (<1yr experience), but ramping up fairly quickly.

I have a function I use to register my handlers:

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...)
	}
}

I have some code that registers named functions as handlers:

func (s *Server) RegisterDefaultHandlers() {
	s.RegisterHandler(&quot;/ping&quot;, Ping)
}

func Ping(w http.ResponseWriter, request *http.Request) {
	responders.RespondOk(w)
}

I also have unit test code that registers anonymous functions as handlers:

s.RegisterHandler(&quot;/testPath&quot;, func(w http.ResponseWriter, r *http.Request) {
    // whatever the test calls for
}, http.MethodPost)

This all works great -- just establishing my starting point.

Today, I find myself banging my head against Go's type system. I am defining some custom handler types, for example:

type UserAwareHandlerFunc func(http.ResponseWriter, *http.Request, models.User)

And I'm also introducing a function to allow me to register such handlers, and to wrap all handlers in context.ClearHandler. If this works, I'll also wrap everything with another function that sets a few things on my logging context. What I have so far:

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(&quot;handler %+v (type %s) is not a recognized handler type.&quot;, 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 https://stackoverflow.com/a/48203334
	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)    
    }
}

With these changes, I can no longer register named or anonymous functions; the type assertions in ApplyWrappers all fail. I have to declare and define a typed variable, then pass that in.

Named functions have two feasible approaches:

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(&quot;/ping&quot;, Ping)

    var pingHandler2 http.HandlerFunc = Ping2
	s.RegisterHandler(&quot;/ping2&quot;, pingHandler2)
}

For anonymous functions, I can do:

var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
    ...
}
s.RegisterHandler(&quot;/testPath&quot;, handler, http.MethodPost)

The whole point of what I've built here is to consolidate boilerplate into one place, keeping my many tests and handlers as streamlined as possible. The need to declare a typed variable is working against that goal. So my question is this: is there some special type magic I could use (preferably in RegisterHandler and/or ApplyWrappers) that would restore the ability to pass named and/or anonymous functions to RegisterHandler?

EDIT: thanks so much for the quick answers. Problem solved:

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(&quot;handler %+v (type %s) is not a recognized handler type.&quot;, 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 https://stackoverflow.com/a/48203334
	result = context.ClearHandler(result)
	return result
}

It's working now, but I still have questions:

  1. If I understand correctly, the "duck typing" behavior that I was looking for here would have been fine if I were dealing with interfaces rather than functions. What drives the distinction? Is duck-typing of functions something I could reasonably hope to see in a future version of the language?
  2. I can cast the anonymous function to a HandlerFunc. It's weird to me that casting and type assertions don't share semantics. Can someone explain?

EDIT 2: I've now seen the bit of the language spec that says that defined types (i.e. ones with names) are never identical to any other type, even if the underlying type is the same, and as such will never work in a type assertion (unless it's an interface). So now I'm left wondering:

  1. Why are the semantics different between interfaces and other types?
  2. Why are the semantics different for named and un-named types?

I find both to be unintuitive and inconvenient. Am I alone here? I'm wondering if anyone knows why these decisions were made when the language was designed (perhaps it's particularly difficult to implement without bloating the compiler or its output, etc.), and if anyone is aware of plans to address either situation.

答案1

得分: 2

http.HandlerFunc是一个特定的类型。Ping函数不属于该类型,尽管它可以转换为该类型,因为它们都具有相同的基础类型。

如果handler是一个匿名函数,那么你需要在类型断言中使用一个匿名函数:

f, ok := handler.(func(http.ResponseWriter,*http.Request))

https://golang.org/ref/spec#Type_assertions

> - 如果T不是一个接口类型,x.(T)断言x的动态类型与类型T完全相同
> - 如果T是一个接口类型,x.(T)断言x的动态类型实现了接口T

英文:

http.HandlerFunc is a specific type. The Ping function is NOT of that type, although it can be converted to that type since they both have the same underlying type.

If handler is an anonymous function then you need to use an anonymous function in the type assertion:

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.

huangapple
  • 本文由 发表于 2021年10月17日 22:45:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/69605448.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定