Named types vs not named types

224 views
Skip to first unread message

Victor Giordano

unread,
Jun 27, 2021, 9:52:39 AM6/27/21
to golang-nuts
Hello gophers!

While studing at this source code in search for some knowledge and enlightment, i do note that in some file a type is defined and then is not used in a place where it could be used. This open an interrogant for me, because tipification is often good thing, regardless the  language  I may state,  and I express it via a ticket. I get the idea that due to language grammar changing the code would be a breaking change. 

But i keep wondering if they actually do this for a reason.. i mean, given the possiblity to get back in time, ¿does the team at golang will write the same source code, definiting a type with a name and then intenttionally not using it? i mean...i keep wondering if there is any reason for defined types and then not use it and using the gitlab channel i probably fail to express my initial intention. I do often read some third party code, in order to view others minds (or try at least..), what i'm asking here is a question in order to get another people point of view.

Thanks again!

Levieux Michel

unread,
Jun 27, 2021, 10:07:58 AM6/27/21
to Victor Giordano, golang-nuts
Hi, 

I'm not sure what type you are referring to here. This file defines many named types and interfaces and I'd say from a quick read that all of them are used.

Could you clarify what you mean?

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/96369719-6200-4765-aee1-83befce04666n%40googlegroups.com.

Axel Wagner

unread,
Jun 27, 2021, 10:46:44 AM6/27/21
to golang-nuts
If I understand you correctly, you are suggesting to replace the parameter type `func(http.ResponseWriter, *http.Request)` with the parameter type `http.HandlerFunc`. You've been (correctly) told that we can't make that change, because it would break the Go 1 compatibility change (as there is code which currently compiles which wouldn't compile after that change). But you are wondering if, *ignoring* the compatibility guarantee, it would be a good change. Am I getting this right?

If so: I don't think it would be a good change.

First, it's important to realize that the *only* reason, `http.HandlerFunc` exists at all, is so that you can write a `func(http.ResponseWriter, *http.Request)` and use it as a `http.Handler`, in the places where `net/http` expects the latter. You say the type isn't used - but it is. It's used by *users* of the `net/http` package, to make their plain functions into `http.Handler`s. It is also used in `net/http` itself - in the exact function you are referring to. That is the exact and only purpose of that type, to make a plain function implement the `Handler` interface. So, taking a plain function as a parameter *is the purpose of having the `HandlerFunc` type*.

You also say that adding types is a good thing. I tend to disagree with that as a general statement. Adding types is a good thing, if it serves as important documentation or if it serves to catch bugs. I don't think either of these would be happening with this change. In terms of documentation - well, you don't *have* to pass a `http.HandlerFunc`, so there is no reason for the documentation to make it clear that you should. You can (and should) just pass a plain `func`. So, using the defined type here wouldn't serve as documentation, it would document the *wrong* thing.

As for catching bugs: Making the parameter type a defined type would only change one thing in terms of type-safety. It would mean that if you define a *different* type `type MyFunc func(http.ResponseWriter, *http.Request)`, the compiler would prevent you from writing `http.HandleFunc(…, MyFunc(f))`. Preventing a bug would thus require that your `MyFunc` type would have to be used semantically differently from `http.HandlerFunc`. But that seems *exceedingly* unlikely, given that you defined `MyFunc` in terms of the `net/http` package. And it would then appear *exceedingly* unlikely, that you'd accidentally mix the two up - almost all usages of `http.HandleFunc` will pass the name of some defined function and that will always work.

But all of this discussion is really moot. It's a breaking change, so it can't happen - whether it's a good change or not doesn't exactly matter at that point. Personally, *if* we could "go back in time" and wouldn't have to worry about backwards compatibility, my vote would rather be to change the language to make the HandlerFunc type obsolete and remove it altogether.

Victor Giordano

unread,
Jun 27, 2021, 11:26:10 AM6/27/21
to Axel Wagner, golang-nuts
Thanks for the answers. I'm not saying that what I proposed in the ticket should be engaged, no! I get the idea why not and I'm okay with that. The related discussion about typification could be addressed on a different thread.
The thing I bring here are my doubts about this: I keep wondering if they code that way for any reason. With "code that way" I mean: define a type and then not use it. 

Just for clarification 
  • If you see here you will find a definition for a function.
  • Then if you look here you will find that definition again as the type for the second argument.
  • So, ¿why not employ the type defined in the first place? Don't get me wrong,but if I define a type I tend to use that type where it appears. That is in fact the basis of making types, to use them. So that feeds my questioning!
 

You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/VBQrlI6-zW0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHnCTf_4G5ZhGX0EXBKJRN9LcEWbKWOdPiCTKdX6SDqPA%40mail.gmail.com.


--
V

Jan Mercl

unread,
Jun 27, 2021, 11:37:44 AM6/27/21
to Victor Giordano, Axel Wagner, golang-nuts
On Sun, Jun 27, 2021 at 5:25 PM Victor Giordano <vituc...@gmail.com> wrote:

> If you see here you will find a definition for a function.

This defines type `HandlerFunc`:
https://github.com/golang/go/blob/37f9a8f69d6299783eac8848d87e27eb563500ac/src/net/http/server.go#L2042

> Then if you look here you will find that definition again as the type for the second argument.

This defines method `HandlerFunc` of type `*ServeMux`:
https://github.com/golang/go/blob/37f9a8f69d6299783eac8848d87e27eb563500ac/src/net/http/server.go#L2473

Both definitions share the same name `HandlerFunc`, but are not
binding the name to the same thing. One is a type name, the other is a
name of method attached to a type. The names are defined in different
namespaces.

I suggest taking a look at the language specification at
https://golang.org/ref/spec. It's IME and IMO the best starting point
for learning Go.

Axel Wagner

unread,
Jun 27, 2021, 11:51:44 AM6/27/21
to Victor Giordano, golang-nuts
On Sun, Jun 27, 2021 at 5:25 PM Victor Giordano <vituc...@gmail.com> wrote:
I keep wondering if they code that way for any reason. With "code that way" I mean: define a type and then not use it. 

As I said: It's used plenty of times. Both inside of `net/http` and outside of it.
  • So, ¿why not employ the type defined in the first place?
I feel like I gave a bunch of reasons for this too.

Axel Wagner

unread,
Jun 27, 2021, 11:56:36 AM6/27/21
to golang-nuts
FWIW, arguing that `http.HandleFunc` should take a `http.HandlerFunc` because there exists a defined type with the same underlying type as the parameter is a bit like arguing every function that takes an `int64` should instead take a time.Duration. That's just not how types tend to work.

It makes no sense for `http.HandleFunc` to take a `http.HandlerFunc`, because it's purpose is specifically to work on a plain function. If you have an `http.HandlerFunc`, you can already just call `http.Handle` - there is no need to make a separate function that takes a *specific* implementation of `http.Handler`.

Victor Giordano

unread,
Jun 27, 2021, 12:06:04 PM6/27/21
to Axel Wagner, golang-nuts
Thanks to all for the answer, i really try to see any actual reason but i still don't get it. For me, to my humble acknowledgement, if I define a type I tend to use everywhere it appears. Period. End of the story.

> FWIW, arguing that `http.HandleFunc` should take a `http.HandlerFunc` because there exists a defined type with the same underlying type as the parameter is a bit like arguing every function that takes an `int64` should instead take a time.Duration

Allow me to put in different words: if you define `func doSomething(duration int64)` at least i will argue why don't employ time.Duration as a type there, if the parameter actually represents a Duration that is also a defined type, ¿you don't?. I won't say the same about other things that hold an int64 that represents for example an ID of record in a database.





--
V

Axel Wagner

unread,
Jun 27, 2021, 12:29:10 PM6/27/21
to Victor Giordano, golang-nuts
On Sun, Jun 27, 2021 at 6:05 PM Victor Giordano <vituc...@gmail.com> wrote:
Thanks to all for the answer, i really try to see any actual reason but i still don't get it. For me, to my humble acknowledgement, if I define a type I tend to use everywhere it appears. Period. End of the story. 

> FWIW, arguing that `http.HandleFunc` should take a `http.HandlerFunc` because there exists a defined type with the same underlying type as the parameter is a bit like arguing every function that takes an `int64` should instead take a time.Duration

Allow me to put in different words: if you define `func doSomething(duration int64)` at least i will argue why don't employ time.Duration as a type there, if the parameter actually represents a Duration that is also a defined type, ¿you don't?.

Yes. But that's the thing - if what the function actually takes a duration, then the correct type is a duration. But the type `http.HandleFunc` takes is *not* a `http.HandlerFunc`, it's a `func(http.ResponseWriter, *http.Request)`. It's a different type and it's the correct type to describe what that function is for. If the type was `http.HandlerFunc`, then `http.HandleFunc` wouldn't need to exist, because `http.Handle` would suffice.

For example, if you had a function

// DurationFromMS returns a time.Duration, based on a duration given as an integer in ms.
func DurationFromMS(d int64) time.Duration {
    return time.Duration(d * 1000)
}

Would you make the parameter type `time.Duration`? After all, it represents a duration, right? But you wouldn't. It would be the wrong type to represent what the function does.

Or, a different example: We could introduce a new type in the `filepath` package:

// Path is a path, using the OS-specific delimiter
type Path string

// Verify makes sure that p is a path, using the correct, OS-specific delimiter.
// It returns p as a Path, and an error, if p was invalid.
func Verify(p string) (Path, error)

We could then have `filepath.Join` etc. take `Path`s, instead of `string`s, to represent that the argument actually must be a valid path, using the OS-specific separator. Which would be different from `path.Path`, of course, which would always use "/" as a separator. Meaning you wouldn't be able to accidentally use one as the other, which would add type-safety.

But should `Verify` take a `Path` here? Of course not. That would be the wrong type. It just returns its argument converted into the correct type, but semantically, it still takes *a plain string*. Before you pass the path into `Verify`, it doesn't have the semantic association of "this string is an OS-specific path" - that's exactly the semantic association that `Verify` creates.

Your argument hinges on the assumption that `http.HandleFunc`s parameter has the semantic interpretation (not only the same underlying type as) as `http.HandlerFunc`. But it doesn't. The semantic interpretation of the argument to `http.HandleFunc` is a plain function. Otherwise, it wouldn't need to exist - because we already *have* a function that can take a `http.HandlerFunc`: `http.Handle`.

The plain func is describing exactly the type that function should take. `http.HandlerFunc` would be the wrong type.

Victor Giordano

unread,
Jun 27, 2021, 2:18:30 PM6/27/21
to Axel Wagner, golang-nuts
Axel, thanks for the insight! You give a lot to think about and that is what was looking for. The path example is very nice btw!. Is like working with a raw data value that needs a little bit validation to ensure it correctines. Like having a type for natural numbers (like 'Natural') that can be created with a method FromInt(x int) error and you validate in it that the x is actually a positive integer. Life is nice when we employ types that lead to more robust code.

I do get `http.HandlerFunc`  means something "bigger" than `func(ResponseWriter, *Request)` because the type comes "augmented" with another method for calling the function with another name thus becoming compliant with the type Handler interface. That is indeed an understandable reason.

But but but... just bear with me... cuz, in this specific case, I guess you could employ both "approaches". See my example here.

Also I feel the need to clarify something about I stated...
> Thanks to all for the answer, i really try to see any actual reason but i still don't get it. For me, to my humble acknowledgement, if I define a type I tend to use everywhere it appears. Period. End of the story.
I have to admit, although I follow this recipe as a compass, I may pass by sometimes and leave redundant definitions. It is okay, after all, we are humans and errors happen.
--
V

Axel Wagner

unread,
Jun 27, 2021, 2:56:18 PM6/27/21
to Victor Giordano, golang-nuts
On Sun, Jun 27, 2021 at 8:17 PM Victor Giordano <vituc...@gmail.com> wrote:
But but but... just bear with me... cuz, in this specific case, I guess you could employ both "approaches". See my example here.

Yes, I'm aware this works. That wasn't in doubt. The question is "is it better". In my opinion, it isn't.

Axel Wagner

unread,
Jun 27, 2021, 3:08:25 PM6/27/21
to golang-nuts
BTW, to be clear: You misuse the term "Untyped" in your example. Both methods are fully typed. They just use different types. This is kind of relevant, because you say "typed is better" - but both are typed to the same degree.

Victor Giordano

unread,
Jun 27, 2021, 3:41:51 PM6/27/21
to Axel Wagner, golang-nuts
Ok. Thanks for the insight again. Correction noted. I do get that types are named or not, and any not named type is a type of its own; and two not named types are different despite having the same definition (like in Pascal)

Nice to clarify the issue. 

Now regarding our friendly discussion...

> The question is "is it better". In my opinion, it isn't.
mmmm my inner philosophical orc is wandering... 🤔... it's not my source code, what I can say... I will probably try to employ an unique type where possible, or the least quantity of types. At least that is what I tried in my source code.




--
V

Victor Giordano

unread,
Jun 27, 2021, 3:44:34 PM6/27/21
to Axel Wagner, golang-nuts
> and two not named types are different despite having the same definition (like in Pascal)

Mmmm.. I really don't know if what I stated is actually true.... perhaps two not named types with the same definition are the same underlying type.

--
V

Axel Wagner

unread,
Jun 27, 2021, 3:58:50 PM6/27/21
to golang-nuts
On Sun, Jun 27, 2021 at 9:41 PM Victor Giordano <vituc...@gmail.com> wrote:
Ok. Thanks for the insight again. Correction noted. I do get that types are named or not, and any not named type is a type of its own; and two not named types are different despite having the same definition (like in Pascal)

Nit: The nomenclature is "defined", not "named" (it changed when type aliases where introduced, which give a type a name, without defining a new type - so "named" became confusing).

With that, there are two aspects relative to this example:
Type identity. A defined type is different to any other type. So, `http.HandlerFunc` and `func(http.ResponseWriter, *http.Request)` are not identical types, because the first is an identical type and it's different from any other type.
Assignability. This is a form of subtyping. In this case, the second rule is relevant: "x's type V and T have identical underlying types and at least one of V or T is not a defined type". In this case, `http.HandlerFunc` and `func(http.ResponseWriter, *http.Request)` have the same underlying type (the latter is the underlying type of both) and the latter is not a defined type. So a `http.HandlerFunc` is assignable to a `func(http.ResponseWriter, *http.Request)` and vice-versa. Which is why your example works - and the opposite example also does.

The fact that the types are not identical is relevant, because it means you can't convert a `[]http.HandlerFunc` into a `[]func(http.ResponseWriter, *http.Request)`, for example. Which is also, why doing this change would be a breaking change. For example, someone might have defined

type Mux interface {
    Handle(pattern string, h http.Handler)
    HandleFunc(pattern string, f func(http.ResponseWriter, *http.Request))
}
var m Mux = http.NewServeMux()

If the type of the `(*http.ServeMux) HandleFunc` parameter was changed to `http.HandlerFunc`, this code would no longer compile, as the parameter types must be identical, not just assignable, for interface satisfaction.

Axel Wagner

unread,
Jun 27, 2021, 3:59:28 PM6/27/21
to golang-nuts
whoops: "…because the first is a defined type", of course.

Victor Giordano

unread,
Jun 27, 2021, 4:29:41 PM6/27/21
to Axel Wagner, golang-nuts
> Nit: The nomenclature is "defined", not "named" (it changed when type aliases where introduced, which give a type a name, without defining a new type - so "named" became confusing).

Note taken.

You give me a lot knowledge's food that I need to digest ... So don't expect me to answer you now, in the short term, I need to re-evaluate my ideas. Thank  you for your concern! I really appreciate it a lot. 





--
V

tapi...@gmail.com

unread,
Jun 27, 2021, 10:10:29 PM6/27/21
to golang-nuts
A some weak reason not to support the change.
Using unnamed parameter types could avoiding some explicit conversions.

package main

import "net/http"

type MyHandlerFunc func(http.ResponseWriter, *http.Request)

func f(http.HandlerFunc) {}

func g(MyHandlerFunc) {}

func h(func(http.ResponseWriter, *http.Request)) {}

func main() {
    var x http.HandlerFunc
    var y MyHandlerFunc
    var z func(http.ResponseWriter, *http.Request)
   
    f(x) // ok
    f(y) // error: need conversion
    f(z) // ok
   
    g(x) // error: need conversion
    g(y) // ok
    g(z) // ok
   
    h(x) // ok
    h(y) // ok
    h(z) // ok
}

Victor Giordano

unread,
Jul 3, 2021, 12:32:03 PM7/3/21
to golang-nuts
Thanks to all for sharing with me insight.
My assessment of the situation until now is:
  • Changing things indded would be a pain as there is to much build on top of it due to grammar.
  • If i could back in time i would write this function signature using this named type for the second argument. I see no harm on doing it that way.
Greetings
Reply all
Reply to author
Forward
0 new messages