-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🤔 Using interface{} to support int or string addresses is confusing #555
Comments
Welcome @peterbourgon! Fiber started as a private project in 2019 to experiment and maybe port some of my microservices running on Express to Go. My goal was to create an identical API, but quickly came to a conclusion this was almost impossible to pull off ( no support for overloading of methods and operators ). I had to make some design choices to use app.Use(handler)
app.Use("/api", handler)
app.Use("/api", handler, handler, handler) If I could go back in time, maybe I would have done it differently. The reason we are still using type conversion or variadic arguments for a small set of methods is to avoid breaking changes for those who are running Fiber in production. I'm happy you opened this issue, this means the API documentation is not enough to avoid confusion. Therefore I suggest we improve the code comments for the After saying the above, we could change this in func (app *App) Listen(address interface{}, tlsconfig ...tls.Config) error {}
func (app *App) Listener(ln net.Listener) error
func (app *App) ListenerWithTls(ln net.Listener, tlsconfig *tls.Config) error
func (app *App) ListenOnPort(port int) error {}
func (app *App) ListenOnAddress(address string) error {}
func (app *App) ListenOnPortWithTls(port int, tlsconfig *tls.Config) error {}
func (app *App) ListenOnAdressWithTsl(address string, tlsconfig *tls.Config) error {} I prefer the first, but let me know what you think 👍 |
Hi @peterbourgon and @Fenny, Thanks for adding the possible signatures in the API documentation. Modifying methods like Thanks for bringing this up nonetheless! 👍🏼 |
This is the heart of the discussion, I think. Given this goal, there's a tension between keeping things familiar to users coming from a different language/framework, and keeping things sensical and idiomatic in the actual language of implementation. I think it's great to try and make a similar API, but if there are situations where creating an identical API would result in highly nonidiomatic code, or code that subverts essential properties of the language, I think it's important to defer to the language. I think it's important because it's ultimately harmful to your users — and to their customers! — to suggest that programming in Go is essentially the same as programming in Javascript. The two languages are sufficiently different that using them successfully — correctly, without bugs, and without performance problems — requires a different mental model, and different programming behavior. In my opinion, this is a case where aiming for an identical API to Fiber has produced code that subverts essential properties of the actual implementation language. Go is not a dynamically typed language, and while the empty interface In dynamic languages, programmers might be used to paying these costs by default — either implicitly, e.g. automatic type boxing/unboxing and the possibility of runtime type errors; or explicitly, e.g. always needing to consult documentation to know what are the valid types for a parameter. But one important and rather central characteristic of statically typed languages is that programmers shouldn't be paying these costs by default. —
I think one point I'm trying to make is that, in statically typed languages, users shouldn't need to consult API documentation to figure out what are the valid types for arguments to a function. Documentation should describe the behavior and semantics of a function, not it's mechanical characteristics. —
Another point I'm trying to make is that, in statically typed languages, a function that supports multiple different types and number of arguments is actually dangerous, not convenient. You're giving up an essential property of the language, which is compile-time type checking, to produce an API that can be expressed equally conveniently as e.g. func (a *App) Use(path string, handlers... Handler)
func (a *App) UseRoot(handlers... Handler) —
With app.Use, there were two valid ways to call it: with an explicit route and set of handlers, or with no explicit route (presumably defaulting to With app.Listen, there are apparently many valid ways to call it, with many combinations of valid inputs. In this case, one common approach is with a config struct. Here's one way that might work. type Config struct {
Listener net.Listener // used by default
Address string // used if Listener is not provided, syntax is ":8080" or "localhost:8080"
TLS *tls.Config // optional
} There is no need to allow both int and string types to be provided as the address parameter — another example of, in my opinion, sacrificing essential type safety for superficial convenience. The config struct approach permits callers to use it, conveniently and with type safety, as e.g. a.Listen(app.Config{Listener: ln}) // one form
a.Listen(app.Config{Address: ":8080"}) // another form
a.Listen(app.Config{Address: "localhost:8000", TLS: &tls.Config{...}}) — I've presented my case, but this is your project, and of course you make the decisions :) |
Hi, @peterbourgon Furthermore, even if the type of address is clearly string, if the user does not read the documentation or source code, there are also many confusions with stdlib's
In short, I don’t think using Thanks. |
In Go,
Yes, that's true. My point was there is a categorical difference between not knowing the acceptable schema of the string, and not knowing what types are acceptable. |
@peterbourgon The And I get your point. I can only say that using an explicit type would be more rigorous. But in terms of use, I feel like I said above, there will be no major problems. Those who understand will know what argument is passed in. I am very happy to discuss with you, and I feel that you have a deeper understanding of programming languages. |
@peterbourgon thank you're providing valuable feedback that might change my mind regarding some design decisions, much appreciated 🍺 This is something that is definitely gonna be discussed when we start drawing our We discussed the possibilities for changes in Thanks! |
This is not strictly a bug, but neither is it a feature request; please feel free to reclassify.
Using the empty interface{} to express "int or string" is highly nonidiomatic and confusing to callers. Go is not a dynamically typed language; its users expect to leverage types to help them read and write their programs. Reading the API docs for this method, it's not clear what type I should pass for address. I see that you allow ints or strings, presumably intended as a convenience, but there's no way for me to know that without reading the source. It's also different to the way essentially every other Listen-type API works, i.e. they take an
addr string
— which, for the record, can be eitherhost:port
or simply:port
to bind on all interfaces.The text was updated successfully, but these errors were encountered: