[HN Gopher] Generics facilitators in Go
       ___________________________________________________________________
        
       Generics facilitators in Go
        
       Author : throwaway894345
       Score  : 148 points
       Date   : 2021-12-22 14:20 UTC (8 hours ago)
        
 (HTM) web link (rakyll.org)
 (TXT) w3m dump (rakyll.org)
        
       | mattarm wrote:
       | "All problems in computer science can be solved by another level
       | of indirection" - Butler Lampson
       | 
       | See https://en.wikipedia.org/wiki/Indirection
        
         | [deleted]
        
           | [deleted]
        
         | infogulch wrote:
         | "Except for the problem of too many levels of indirection" - ?
        
           | adgjlsfhk1 wrote:
           | No, you solve that problem by adding another layer of
           | indirection (aka a caching layer)
        
       | legulere wrote:
       | > Go type system is not a traditional type system
       | 
       | How so, I don't see anything special about Go's type system in
       | the linked article.
        
         | noah_buddy wrote:
         | I believe they're talking about the semi-duck typing used in go
         | for interfaces but I'm not certain. Interfaces are implicitly
         | implemented with a runtime check determining whether a type x
         | assigned to interface y is valid.This is why you'll often see
         | an empty declaration in packages for structs intended to
         | implement an interface like so:
         | 
         | var _ y = x{} // syntax might be slightly wrong
         | 
         | After writing this, I found the linked article in the OP link
         | to what the author meant.
         | 
         | https://rakyll.org/typesystem/
         | 
         | There's really a bunch of small things that are different about
         | golang that individually you can find elsewhere but are on the
         | whole unique.
        
       | jayd16 wrote:
       | Reminds me of Java's use of listener objects because it lacked
       | (at the time) lambdas and method references for callbacks.
       | 
       | Hopefully Go generics continue to improve.
        
         | mcculley wrote:
         | I don't think I understand what you mean. What would have been
         | different about listener interfaces if lambdas and method
         | references had been available?
        
         | kubb wrote:
         | I wonder if the limitation of not allowing type parameters for
         | methods came from wanting to prevent people from using it for
         | certain features that would be considered "too complicated for
         | Go", or from it being "too complicated to support" or from "we
         | won't be needing it anyways".
         | 
         | Another option to circumvent this is to use top-level
         | functions, and accept the receiver as the first argument of the
         | function. This is effectively the same thing, the only thing
         | it's missing is the object.method() syntax that's familiar to
         | OO programmers.
        
           | randomdata wrote:
           | No need to wonder. The proposal goes into lengthy detail as
           | to why they are not included.
           | 
           | There is hope that they will be added in the future, but
           | solutions to the problems will need to be found first.
        
             | kubb wrote:
             | I wouldn't call it lengthy, but if anyone is wondering, the
             | proposal points out that parametrized methods don't have an
             | obvious behavior _inside interfaces_. No mention of why
             | they left them out in non-interface types.
        
               | Ericson2314 wrote:
               | Yeah they are just being ignorant once again. Even
               | "static" interface usage, a la Rust Traits, is fine. But
               | Go doesn't distinguish between the static stuff and the
               | dynamic vtable stuff very well at all.
               | 
               | That's the problem they should fix.
        
               | randomdata wrote:
               | _> Yeah they are just being ignorant once again._
               | 
               | That's the core problem. Anyone who truly understands
               | generics has shown no willingness to work on Go. The only
               | reason it has progressed as far as it has is because a
               | third-party consultant was temporarily hired to develop
               | what was dubbed Featherweight Go to act as a proof of
               | concept.
               | 
               | It turns out that the intersection of people who care
               | about generics in Go and those those who aren't armchair
               | coders unable to comprehend the domain themselves is
               | limited to very few people. As you know well from your
               | armchair.
        
               | saghm wrote:
               | It seems plausible that some people who might otherwise
               | want to have decided not to given the very vocal
               | contingent of the Go community that's been opposed to
               | generics. After almost a decade of people saying Go
               | didn't need generics, it seems like it would be hard to
               | convince people who are knowledgeable in that field that
               | their efforts would be welcome.
        
               | randomdata wrote:
               | The core Go team has been working on generics since the
               | pre-1.0 days and have submitted many different generics
               | proposals over the years to reflect that. They all, until
               | the latest one, failed for one reason or another, but
               | shared property of them all was being created by those
               | who were not experts in the domain. It is why the latest
               | incarnation brought in an external consultant to help
               | guide the team in the right direction.
               | 
               | There was clearly always interest from the project
               | itself. There may have been some random naysayers on the
               | internet who act like they are opposed, but you'll find
               | people acting every way imaginable on the internet. I
               | really doubt anyone actually refrained from working on
               | the project because an internet troll wrote something
               | silly.
        
               | philosopher1234 wrote:
               | You sound like someone who doesn't know very much about
               | Go.
        
               | mseepgood wrote:
               | In Go any type can implement interfaces. A type doesn't
               | declare which interfaces it implements.
        
       | kubb wrote:
       | > Go type system is not a traditional type system and it was not
       | possible just to bring an existing generics implementation from
       | other language and be done.
       | 
       | I claim that there are no original ideas in Go's type system and
       | in the way that Go does generics, and the reason it took so long
       | was because it came out of an environment with actors (including
       | a part of the community) pushing against the feature.
       | 
       | You might disagree, I'm curious what original features of Go you
       | will point out to me.
        
         | throwaway894345 wrote:
         | This seems like a straw man. I don't think anyone has claimed
         | Go's type system has novel features.
         | 
         | > the reason it took so long was because it came out of an
         | environment with actors (including a part of the community)
         | pushing against the feature.
         | 
         | The reason it took so long is because it simply wasn't the
         | biggest fish to fry. Generics are a massive feature, and there
         | were a lot of other opportunities to deliver more value for
         | considerably less work. Of course, there was a healthy amount
         | of pushback against _rushing_ generics, considering generics
         | must interact with every other feature in the type system even
         | if those features aren 't novel.
         | 
         | Frankly, people make too big a deal about generics. It will be
         | a neat quality of life improvement for certain kinds of code,
         | and a lot of additional code will enter the ecosystem which is
         | gratuitously abstract. Builds will probably get a fair bit
         | slower on average while still being fast relative to most other
         | languages. Frankly, no type system feature will ever impact a
         | language as significantly as things like tooling quality,
         | ecosystem, runtime performance, static+native compilation,
         | iteration loop time, learning curve, etc.
        
           | masklinn wrote:
           | > I don't think anyone has claimed Go's type system has novel
           | features.
           | 
           | The third phrase of the linked essay is:
           | 
           | > Go type system is not a traditional type system
           | 
           | "non-traditional" is generally read as "novel" in some way.
        
             | Vendan wrote:
             | That's a bit of a stretch... If someone made a language
             | that directly copied Haskell's type system, I'd 100% call
             | it a non-traditional type system, but there would be
             | nothing novel about it.
        
             | throwaway894345 wrote:
             | You're mistaken here. "non-traditional" doesn't mean
             | "novel", it just means "something that isn't traditional".
             | For example, eating cereal for dinner is non-traditional,
             | but that doesn't mean no one has done it before.
        
           | kubb wrote:
           | I'm quoting the author directly. I didn't tamper with the
           | quote.
        
             | throwaway894345 wrote:
             | I agree that you didn't tamper with the quote, but you
             | clearly weren't rebutting it either. Rather, you were
             | rebutting the idea that Go's type system contains novel
             | features, which the author didn't allege.
        
           | eweise wrote:
           | I'm currently working on a process that has to manipulate
           | collection of data and its really aggravating to invent
           | custom functions to do basic manipulations such as groupby,
           | maps, filter, etc. It basically feels like java back in the
           | early 2000s.
        
           | jerf wrote:
           | "Frankly, people make too big a deal about generics."
           | 
           | I think there's a lot of confusion brought in from languages
           | where generics are a bigger deal than they are in Go, because
           | when generics came in to those languages it was like turning
           | on a light.
           | 
           | However, if you map out the _problem space_ of  "what did
           | generics solve in those languages", rather than obsessing
           | about the _solution_ , what you find is that Go's interfaces,
           | combined with the structural typing ("static duck typing"
           | basically, rather than having to explicitly declare
           | conformance at declaration time) covered quite a bit of the
           | problem space. And that's what matters, whether the problem
           | space is covered, not whether the problem space was covered
           | in exactly the same way as some other language.
           | 
           | People who either have never programmed in Go, or only spent
           | long enough with it to try to program "(Not Go) in Go" (e.g.,
           | "Javascript in Go"), and didn't learn how to use the tools Go
           | has to cover the problem space, end up vastly overestimating
           | their utility in Go, and vastly overestimating the sort of
           | problems you can't use Go to solve. They're like "How can you
           | program in a language that doesn't cover the 'generics'
           | problem space?", and the answer is, "You're actually right, I
           | couldn't! But I can use Go, because it actually does cover
           | quite a bit of the problem space, just in a different way
           | than what you're demanding." Interfaces DO NOT cover all the
           | space, I would never dream of claiming otherwise, but they
           | cover a _lot_ of the space. There 's even some problems where
           | I've found Go simply outright the best solution because of
           | some particular thing I can use interfaces to solve.
           | 
           | I'll actually kind of miss the "How can I do this without
           | generics in Go?" questions on /r/golang. They're fun, and
           | with the exception of "data structures", generally I didn't
           | just come up with "a" solution, but with a _good_ solution.
        
           | phillipcarter wrote:
           | > Frankly, people make too big a deal about generics.
           | 
           | Different strokes for different folks, but I think generics
           | are a _huge_ deal. They make libraries so much more ergonomic
           | and reduce toil generally. It 's a case of dealing with many,
           | many small papercuts over a long period of time rather than a
           | big-bang sort of thing.
           | 
           | > tooling quality
           | 
           | I'm a huge proponent of tools, so much so that I worked on
           | programming language tools for 5 years. My perspective is
           | that tools are there to optimize an existing experience, but
           | reach a local maxima in the overall experience because
           | they're limited by language semantics. When you improve
           | language semantics, tools get to improve experience again.
           | That's why it's so important to do language design in a way
           | that _strongly considers_ the tooling experience, and a big
           | reason why you see so much love for languages like TypeScript
           | and C#. They work hand in hand. And generics are an example
           | of how semantics can push tools to be better.
        
             | throwaway894345 wrote:
             | When I think about tooling, I'm thinking about things like
             | build systems and dependency managers. In Go, I don't need
             | to script my own build system like I would do in C, C++,
             | Java, etc. Similarly, reproducible package management works
             | out of the box--I don't need to try in vain to get the
             | right versions of the right dependencies installed to the
             | right locations like I do in C and C++ (and Python, to a
             | lesser extent). Moreover, Go produces native static
             | binaries out of the box by default--I don't have to cobble
             | together a bunch of configuration in every one of my
             | projects only to have it broken by the odd transitive
             | dependency which can't be statically compiled (well, this
             | is probably the case in some Go packages, but I contend
             | that it's far rarer than in the Java, Python, Rust, etc
             | worlds--certainly I've never run into this in my decade
             | with Go). Further still, in Go, I don't have to write CI
             | pipelines to build and publish code packages nor
             | documentation packages.
             | 
             | And then there is the toil reduced by having good
             | performance. I have about 15 years of experience with
             | Python, and almost invariably there's some hot path in the
             | code which needs something between Python's level of
             | performance and Go's level of performance. Unlike Go,
             | Python affords relatively little headroom for optimization
             | --you can't easily parallelize anything or pull some
             | allocation out of a tight loop for a 10x performance gain.
             | Usually this looks like rearchitecting your codebase so
             | that "rewrite the hotpath in C/multiprocessing/etc" is
             | attainable and on top of that you're probably implying
             | significant changes to your build system and/or runtime
             | environment as well as a load of maintainability problems
             | associated with anything that touches the language/process
             | boundary.
             | 
             | By comparison, I think the gains from generics are
             | relatively small.
        
               | phillipcarter wrote:
               | > I'm thinking about things like build systems and
               | dependency managers.
               | 
               | Ah, that's a different kind of tooling. This matters, but
               | there are loads of developers out there who care just as
               | much (if not more) about IDE tooling and other things
               | that assist in the writing and refactoring of source code
               | itself. Features like generics open up new ways to make
               | this tooling better.
               | 
               | It's also worth pointing out that you can have your cake
               | and eat it too. .NET and Rust are fine examples of having
               | great build/package management tooling, runtime
               | performance, compiler performance (.NET far more than
               | Rust), artifact management, and so on. And great IDE
               | tooling in different environments.
        
               | throwaway894345 wrote:
               | Yes, I agree that IDE tooling is nice and that it doesn't
               | conflict with having good build tooling; my only point is
               | that, given the choice between good tooling and generics
               | (or any type system feature), I would choose the tooling
               | hands down every single time.
        
               | phillipcarter wrote:
               | Why make it an either/or thing? We should demand more of
               | corporate-controlled software. The money exists to make
               | both a reality.
        
         | verdverm wrote:
         | While not necessarily part of the language, but arguably part
         | of Go, something novel is the dep mgmt system. I have not
         | encountered the MVS algorithm in another language. CUE is
         | intending to adopt it.
        
         | jjtheblunt wrote:
         | to your point, isn't Go just the Google-funded continuation of
         | Bell Labs' Limbo, with essentially the same people?
         | 
         | If so, it's curious how sparsely mentioned the origin at Bell
         | Labs pre Google is.
         | 
         | https://en.wikipedia.org/wiki/Limbo_(programming_language)
        
         | skybrian wrote:
         | Unusual doesn't mean unique or better, just different enough to
         | make doing a straight port harder.
         | 
         | For example, Go's interface types and its lack of constructors
         | seem different from most languages you might borrow from.
        
         | [deleted]
        
         | 1_player wrote:
         | What is the point of going into Go generics posts on HN and
         | making a point to mention that core Go developers were against
         | the feature, which is incorrect, but also shows there's some
         | axe to grind against that team. Is that to show they're less
         | smart then they think they are?
         | 
         | I swear there's at least one top-level comment such as yours in
         | every thread, for no reason other than just to discredit
         | people's work. I find it disgraceful.
        
         | randomdata wrote:
         | Go's generics have been in development (by the same person)
         | since the pre-1.0 days, with various proposals submitted for
         | their inclusion. Are you suggesting that those earlier
         | proposals failed for frivolous reasons rather than real
         | technical concerns?
         | 
         | It seems to me that that it was less about active pushback and
         | more that people who cared about having generics in Go was
         | limited to an _exceedingly small_ group of people who weren 't
         | necessarily experts in the subject matter. In fact, the Go team
         | hired an external expert to lay the groundwork for what became
         | the winning proposal.
        
         | anonymoushn wrote:
         | A great innovation of Go is that it includes a built-in
         | reference type with both inner nils and outer nils.
        
           | Skunkleton wrote:
           | As a non-serious user of Go, this was something I found to be
           | a negative. What am I missing?
        
             | masklinn wrote:
             | That an innovation is not necessarily positive. For
             | instance INTERCAL innovated COMEFROM.
             | 
             | Also that GP was probably sarcastic when they nominated
             | typed nils.
        
           | Ericson2314 wrote:
           | Well played :)
        
           | kubb wrote:
           | It is a bit like                 void *outer = 0; // outer
           | can be nil       int val = 5;       int *inner = &val; //
           | inner can also be nil       outer = (void*)(&inner) // can
           | have an inner value, inner nil or outer nil
        
             | masklinn wrote:
             | It's not really like that, this is two nested but different
             | values which can be nil (not exactly independently but
             | close).
             | 
             | typed nils are a single value which is both nil and non-nil
             | at the same time.
        
               | throwaway894345 wrote:
               | You're mistaken. An interface is simply a reference type
               | --like other reference types, it may refer to a type
               | which is itself a reference type. The outer reference may
               | be not-nil while the inner reference may be nil, which
               | confuses people who don't understand references into
               | believing that there is a single value which is both nil
               | and not-nil at once.
               | 
               | It's no different than `var x **int` (a variable called
               | "x" whose type is a pointer to a pointer to an int). If
               | you want to safely access the underlying integer data,
               | it's not sufficient to check that the outermost reference
               | is not nil--you must also verify that the inner reference
               | is not nil. We run into this in _every_ language with
               | nullable reference types, including C, Java, Python,
               | JavaScript, etc.
               | 
               | EDIT: `*int` -> `**int`. TIL HN converts two asterisks
               | into one.
        
               | masklinn wrote:
               | > An interface is simply a reference type--like other
               | reference types, it may refer to a type which is itself a
               | reference type. The outer reference may be not-nil while
               | the inner reference may be nil, which confuses people who
               | don't understand references into believing that there is
               | a single value which is both nil and not-nil at once.
               | 
               | None of that is correct. The value-part of the fat
               | pointer is the exact same in both situations: nil. It
               | _is_ a single value which is both nil and non-nil.
               | 
               | > It's no different than `var x **int`
               | 
               | I assume you meant that rather than what you actually
               | wrote.
               | 
               | It is in fact quite different.
               | 
               | > If you want to safely access the underlying integer
               | data, it's not sufficient to check that the outermost
               | reference is not nil--you must also verify that the inner
               | reference is not nil.
               | 
               | Something a typed nil does not let you do, unless you
               | know (or reflect) the underlying concrete type in order
               | to downcast the interface back to a regular pointer.
               | 
               | > We run into this in every language with nullable
               | reference types, including C, Java, Python, JavaScript,
               | etc.
               | 
               | We really don't. In "every language" you have to actually
               | create a double indirection for something within the same
               | heavenly realm (but is not at all the same thing) to
               | occur.
        
               | throwaway894345 wrote:
               | > I assume you meant that rather than what you actually
               | wrote.
               | 
               | Yes, today I learned HN replaces double-asterisks with
               | single asterisks. Edited my original post.
               | 
               | > None of that is correct. The value-part of the fat
               | pointer is the exact same in both situations: nil. It is
               | a single value which is both nil and non-nil.
               | 
               | I repeat, an interface isn't "both nil and non-nil", but
               | rather the nullity of the interface and the nullity of
               | the underlying data are distinct propositions. For
               | example, there is nothing analogous to "both nil and non-
               | nil" when the runtime type is a value type, such as an
               | integer.
               | 
               | An interface behaves just like a reference. When the
               | underlying data is also a reference, then it behaves like
               | a reference to a reference. This means you can have a nil
               | interface or a non-nil interface which references a nil
               | pointer.
               | 
               | Under the covers, Go implements this behavior by
               | representing interfaces as a tuple where the first field
               | contains a pointer to the runtime type information and
               | the second field contains a pointer to the data. Go
               | encodes a nil interface as (nil, nil) while it encodes an
               | interface backed by a nil int pointer as `(<pointer to
               | `int` type>, nil)`.
               | 
               | > We really don't. In "every language" you have to
               | actually create a double indirection for something within
               | the same heavenly realm (but is not at all the same
               | thing) to occur.
               | 
               | Go interfaces to pointer data are functionally "double
               | indirection". The outer pointer can be either nil or a
               | pointer to the data which itself can be nil or valid.
               | 
               | > Something a typed nil does not let you do, unless you
               | know (or reflect) the underlying concrete type in order
               | to downcast the interface back to a regular pointer.
               | 
               | Well, this would be unsafe as the runtime type of the
               | underlying data isn't guaranteed to be a pointer. So by
               | default Go requires runtime type checks in this case, but
               | you can always opt out of these runtime type checks via
               | `unsafe` but this is usually inadvisable.
        
           | jerf wrote:
           | They are not the same nil: http://www.jerf.org/iri/post/2957
        
           | azth wrote:
           | A negative innovation, sure :P
        
           | throwaway894345 wrote:
           | Go has no such feature. Go's interfaces are a reference type
           | which point to the runtime implementation. If that
           | implementation is itself a pointer type, then that pointer
           | may also be nil. Any language that has nullable reference
           | types (including languages in which everything is a pointer
           | like Java or Python) will have this problem.
           | 
           | EDIT: would really like to know why I'm being downvoted. If
           | you disagree with my comment, then best of luck to you in
           | rebutting it. :)
        
             | masklinn wrote:
             | > Go has no such feature. [...] Any language that has
             | nullable reference types (including languages in which
             | everything is a pointer like Java or Python) will have this
             | problem.                   var i *int = nil         var x
             | interface{} = i         if x != nil {
             | fmt.Printf("%v was not nil", x)         }
             | <nil> was not nil
             | 
             | By all means, do demonstrate how Java or Python have this
             | problem.
        
               | Joker_vD wrote:
               | It's much worse, because there is auto-conversion between
               | concrete types and interfaces you can't reliably do e.g.
               | "if err != nil { SendString(err.Error()) }" -- it can
               | panic with SIGSEGV. I've ran into it a month ago in
               | actual production code: condensed version is at [0]. Yes,
               | it's a bad design on the 3rd-party library's part... or
               | perhaps it _is_ a defect of the language:  "pit of
               | success" and all of that, you know?
               | 
               | [0] https://go.dev/play/p/D_M1geUtJTP
        
               | throwaway894345 wrote:
               | I don't think this is a different (and thus not "worse")
               | case--it seems like a specific instance of the same case
               | we've been discussing. Ultimately it comes down to this:
               | error.Error((*CustomErrorType)(nil))
               | 
               | You're putting a nil `*CustomErrorType` into an `error`
               | interface.
               | 
               | This definitely causes some people confusion and results
               | in bugs, but the question under discussion is whether the
               | original claim ("an interface can be simultaneously nil
               | and not nil") is true. Of course, it's not true as this
               | example illustrates--the interface is not nil but the
               | underlying data (of type `*CustomErrorType`) _is_ nil.
        
               | TheDong wrote:
               | I think there is a point to be made here.
               | 
               | Let's say I have the following code:
               | func open() (*os.File, *os.PathError) { return
               | os.NewFile(1, "stdout"), nil }              // in main
               | // (some lines omitted)         fi, err := open()
               | if err == nil { fmt.Println("no error")
               | 
               | So, my question is, does that print 'no error' or not?
               | 
               | Normally, you would say "no, it does not, 'err' is a nil
               | '*os.PathError' so it does not.
               | 
               | However, I didn't mention what lines were omitted, and
               | those lines actually matter!                   // main
               | _, err := os.Executable()                  fi, err :=
               | open()         if err == nil { fmt.Println("no error")
               | 
               | Having that line before it changes the output, and the
               | error is no longer detected as nil (since it's now a non-
               | nil error interface referencing a nil struct).
               | 
               | Why does this matter? Well, it's extremely common in go
               | (idiomatic in fact) to reuse the "err" variable name, and
               | it's also extremely common to use it in multiple
               | assignment with ":="... where despite using the
               | declaration syntax, you can re-assign to existing
               | variables without their types changing.
               | 
               | I think that's what the parent was pointing out: a
               | combination of type inference and automatically
               | "converting" a struct into an interface for you (both in
               | assignment, as I showed above, and when you return a
               | value, as shown in the parents example) means that you
               | can end up with this issue without doing anything as
               | obviously wrong.
               | 
               | In other languages, returning concrete error types means
               | the caller has more type-system information and is good.
               | In go, returning concrete error types gives the caller
               | more information, but opens them up to accidentally
               | implicitly casting it to an interface and blowing their
               | foot off.
               | 
               | As such, we have almost completely untyped error returns,
               | and any time a gopher thinks "this is dumb, why don't I
               | use the type system for this?" they're actually just
               | loading a shotgun and pointing it at their foot.
               | 
               | > I don't think this is a different (and thus not
               | "worse") case
               | 
               | All the above was just to say, I think it's the same
               | case, but it's pointing out that it's "worse" because it
               | interacts poorly with other parts of the go language
               | spec.
               | 
               | Playground: https://go.dev/play/p/4MYEZr8D5Qv
        
               | anonymoushn wrote:
               | I think the original claim is that it can be inner nil or
               | outer nil and those are two different things and often
               | you should check for both.
        
               | throwaway894345 wrote:
               | Sure:                   class Ptr:             def
               | __init__(self, x):                 self.x = x
               | x = Ptr(None)              if x != None:             #
               | Go's `%v` print directive unpacks interfaces.
               | # We'll use `x.x` to emulate the same here
               | print(f"{x.x} was not None")
        
         | phillipcarter wrote:
         | > You might disagree, I'm curious what original features of Go
         | you will point out to me.
         | 
         | I think only insiders on the Go team during that time period
         | can really say for sure what the deal is.
         | 
         | But I will say that retrofitting generics onto a language and
         | standard library is a project of enormous scope and incredible
         | difficulty. So I'm pretty sympathetic to the idea that it took
         | them a while.
        
           | krzat wrote:
           | Thoughts and prayers to the Go team. I suspect they got kinda
           | forced into implementing this, otherwise generics would be
           | available from the start.
        
             | phillipcarter wrote:
             | I would say - congratulations to the Go team! It's a huge
             | accomplishment and a testament to the quality of their
             | software engineering process.
        
       | assbuttbuttass wrote:
       | A nice pattern that will probably become common.
       | 
       | I assume the limitation on generic methods is just an
       | implementation issue, the vtable for an interface has to have a
       | known number of methods, and they can't insert specializations
       | for each type on the fly into the vtable.
        
         | jerf wrote:
         | There is discussion of this in the proposal:
         | https://go.googlesource.com/proposal/+/refs/heads/master/des...
         | 
         | I'd call it a fundamental semantics issue. That discussion is
         | mostly framed in terms of the compile-time problems that arise,
         | but that brief aside about the reflection package is
         | deceptively short, because it means that the entire compile
         | time set of problems is also lifted into runtime, where the
         | problem is even worse. (To translate for someone who isn't a Go
         | expert, the reflection package has enough power that it can
         | dynamically instantiate new types that the compiler did not and
         | can not even in principle have seen at compile time, which may
         | then require new methods to be created. The current Go runtime
         | has no run-time code creation capabilities. [1]) Without the
         | reflection problem, it's annoying but at least solvable in
         | principle, but I don't see any reasonable design (without a
         | fundamental overhaul to specify a runtime-available compiler in
         | the language spec itself, which is a _huge_ step) that solves
         | the reflection issue.
         | 
         | [1] Which _personally_ I consider almost a feature. YMMV.
         | Runtime compilation is very cool in a lot of ways but is a hell
         | of a subsystem to try to secure, just by its very nature. There
         | 's a lot to be said for simply not having that capability
         | unless it is integral to some particular program.
        
           | _old_dude_ wrote:
           | > it means that the entire compile time set of problems is
           | also lifted into runtime
           | 
           | It's what Swift does if inlining is defeated, the type
           | arguments are passed at runtime as a classical argument on
           | stack.
           | 
           | You don't need full reflection as you suggest, only to be
           | able to materialize the types as structs + an interface at
           | runtime. Yes, it's a little slower but most of the overhead
           | comes from the function not be inlined.
        
           | klodolph wrote:
           | Yes, I agree. I think one of the key problems here is that
           | any solution would create a distinction between polymorphic
           | and monomorphic methods. This is why languages are so much
           | more than a bag of features... every feature interacts with
           | other features, often in subtle ways. For example, take a
           | "simple" feature like operator overloading... if you look at
           | how operator overloading _actually_ works in C++ and C#, it
           | 's an incredibly complex feature in both languages, with some
           | surprising behavior. That surprising behavior arises because
           | features interact in weird ways that you wouldn't ordinarily
           | think about.
           | 
           | Saying "no generic methods" is a nice out.
        
       | arwineap wrote:
       | Not terribly excited for generics.
        
         | sigmonsays wrote:
         | I have this concern too. I wish to remain optimistic though.
         | 
         | i've enjoyed go for its simplicity. At times it requires more
         | code (or code generation) but that generated code was easier to
         | debug and have tooling operate on.
         | 
         | With generics, Some things will suffer. Complexity of code and
         | tooling increases, tooling is probably going to suffer and
         | become more costly. compile time and performance will probably
         | also be affected.
         | 
         | Generics are likely only great for collections of things, ie
         | []*T, where, you need to do the same thing on many of T.
        
       | Groxx wrote:
       | You can also cast between these "facilitators", since the unused
       | type doesn't affect memory layout:
       | https://gotipplay.golang.org/p/2w2y1KEjXVE (from
       | https://news.ycombinator.com/item?id=29586456 )
       | 
       | I think they'll be pretty common. It's a simple structure, and
       | simple to manipulate.
        
       | parhamn wrote:
       | I recently saw a go generic data structures library in the wild:
       | https://github.com/zyedidia/generic.
       | 
       | Anyone have any more? I'm curious what people come up with for
       | goroutines/channels.
        
         | mappu wrote:
         | You'll notice the functional iterators under iter/ don't
         | include benchmarks.
         | 
         | Go will not inline a function that exceeds complexity metrics,
         | and one of those metrics is whether the function contains a
         | range statement. You will get a real heap-allocated closure
         | invocation on each loop.
         | 
         | Go's function calls are not that cheap, and these will be
         | measurably slower than the for-loop equivalent. I see
         | functional idioms as being a huge risk factor for Go.
        
           | felixge wrote:
           | > Go will not inline a function that exceeds complexity
           | metrics
           | 
           | True, but:
           | 
           | > and one of those metrics is whether the function contains a
           | range statement. You will get a real heap-allocated closure
           | invocation on each loop.
           | 
           | I think this is no longer true in Go 1.18, see https://github
           | .com/golang/go/issues/14768#issuecomment-98175... .
        
           | zyedidia wrote:
           | Author here. I think there is definitely room for improvement
           | in the iterator API. I intend to experiment with more
           | implementations in the coming weeks with the goal of settling
           | on something better by the time Go 1.18 is released. The main
           | alternative I have in mind is a cursor-style iterator (for
           | example, with `Next`, `Done`, and `Value` methods). If you
           | have something else in mind too, let me know and I might be
           | able to try it out.
        
         | kubb wrote:
         | This is actually a pretty neat collection of data structures!
         | Goes to show what people can do given the opportunity.
        
         | ghughes wrote:
         | This has been useful: https://github.com/rakeeb-
         | hossain/functools
         | 
         | It contains generic implementations of                   Any
         | All         Count         Filter         ForEach         Map
         | Reduce         ReduceRight         Sum         Chunk
        
         | nkcmr wrote:
         | I made this: https://github.com/nkcmr/async so I can experiment
         | with the idea of Promises in Go. Keep in mind it's experimental
         | and I've only lightly played around with the pattern.
        
           | jerf wrote:
           | Go doesn't have much use for promises, and generics won't
           | change that, because channel covers the problem space that
           | promises cover.
           | 
           | It is obvious that a channel is not the same thing as a
           | promise. They are quite different in many ways. But the
           | _problems_ that you solve with promises in some languages are
           | solved with channels in Go. There 's pros and cons to each,
           | but the cons of channels aren't all that significant in the
           | specific context of Go and there are some compelling pros to
           | the channels in the specific context of Go, so there isn't a
           | very fertile space left over for a promise library. I've
           | already seen half-a-dozen go by (non-generic, but missing
           | generics aren't the problem) and those are just the ones that
           | get posted to reddit.
           | 
           | I advise writing Go in Go, and not Javascript in Go. But it's
           | your codebase.
           | 
           | (Promises are basically an inner platform [1] for
           | functionality not provided by the base language. Go provides
           | the requisite functionality in the base language. I think I'm
           | more hostile to inner platforms every year, but at least when
           | you're adding capabilities to the base language you can't get
           | any other way there's a debate to be had. Adding an inner
           | platform to get functionality that already exists is just a
           | bad plan.)
           | 
           | [1]: https://en.wikipedia.org/wiki/Inner-platform_effect
        
             | xiaq wrote:
             | Promises aren't inherently an "inner platform". You can
             | certainly make them part of the "base language", like Rust.
             | JavaScript didn't have async functions and promises in the
             | beginning, but it integrates with the rest of the language
             | and runtime well enough to not feel like something tacked
             | on (well, at least by the standard of JavaScript language
             | design :). Whether async function + promises is a better
             | model than goroutine + channels is another issue.
             | 
             | What you said is accurate in the context of Go though.
             | Having promises in Go will create an "inner platform". If
             | one wants to nitpick, there may be certain workloads that
             | would perform better with async functions and promises, but
             | the design philosophy of Go would happily trade a small
             | amount of performance for simplicity.
        
               | jerf wrote:
               | "Promises aren't inherently an "inner platform". You can
               | certainly make them part of the "base language", like
               | Rust."
               | 
               | That's fair, and I will try to update my internal mental
               | model and stop unconditionally referring to them as such.
               | Thank you.
               | 
               | (My primary objection to them is the way they throw away
               | the stack and throw away structured programming as a
               | result, but they're at least _less_ bad if they aren 't
               | _also_ an inner platform. :) )
        
           | Zababa wrote:
           | That's interesting, I always found Go's handling of
           | asynchronous stuff way better than other languages,
           | especially the ones that relies on async.
        
             | cube2222 wrote:
             | For those interested to learn more about Go's handling of
             | async, I wrote a comment a while ago where I describe why I
             | too think the Go model is superior:
             | https://news.ycombinator.com/item?id=27545031
             | 
             | EDIT: Superior for most non-performance sensitive use
             | cases, that is.
        
               | Zababa wrote:
               | I personally prefer Go's handling of async too. I'm not
               | sure if I would call it superior, because from what I
               | understand monadic async has its uses, especially when
               | you don't have a runtime. But for the kind of programming
               | that I do, which doesn't need every last bit of
               | performance, monadic async is a pain.
               | 
               | For people not familiar with "monadic async", it's the
               | formal way to refer to the function coloring problem [1].
               | Monads offer you ways to "lift" your functions/values in
               | the monadic world (paint the blue values red), but you
               | can't do the opposite (paint the red functions/values
               | blue).
               | 
               | [1]: http://journal.stuffwithstuff.com/2015/02/01/what-
               | color-is-y...
        
               | [deleted]
        
       | svnpenn wrote:
       | I really dont like this. It just feels like people are trying to
       | ram a square peg into a round hole. Personally I dont get why
       | generics were needed in the first place. Every single example Ive
       | seen so far, could be pretty easily replaced with `interface{}`.
       | And now that we finally have generics, people still arent happy.
       | Now that they have their round peg, they are trying to now ram it
       | into a star shaped hole, as is being done in this post.
       | 
       | I really wish people would just use the language as it is. Yes,
       | it does cause for some repetition, but thats not the worst thing
       | in the world. Compared to some Java codebases I have seen, Go is
       | extremely simple already, in terms of "flatness" in the
       | filesystem, and flatness in the type system. I think people
       | should just know when to leave well enough alone. I dont think
       | the complexity of generics is worth it for 99/100 use cases.
        
         | jjtheblunt wrote:
         | Aren't the language builtins (map[X]Y etc) themselves generic ?
        
         | fsdjkflsjfsoij wrote:
         | > Every single example Ive seen so far, could be pretty easily
         | replaced with `interface{}`
         | 
         | Of course they could just use interface{}... But by doing that
         | they lose both type safety and performance.
        
           | svnpenn wrote:
           | > they lose both type safety and performance
           | 
           | I guess you think generics is a magic pixie dust that has the
           | exact performance envelope as non-generic code?
        
             | sidlls wrote:
             | Not exact in all cases, but in some and very close in most
             | others. And it isn't "magic pixie dust"--it is a lot of
             | hard work to implement generics in a way that is both
             | ergonomic for the programmer and efficient.
        
               | cle wrote:
               | I don't think OP was implying that it's not hard work,
               | but that many folks act as if there are no perf tradeoffs
               | with generics, when there are.
        
               | adambatkin wrote:
               | It depends. There is always an increased demand for
               | memory (which can impact the whole hierarchy) but other
               | than that it shouldn't be bad. And I think the point is
               | that compared to interface{}, generics will almost always
               | win. And realistically, compared to hand-rolled, you'd
               | need to hand-roll (or codegen, whatever) multiple
               | implementations which, as machine code, would end up
               | exactly the same as the generic implementation.
               | 
               | And what are we optimizing for anyway? Reading a bunch of
               | code that uses an off-the-shelf library's Stack or Tree
               | or whatever is always going to be easier to read than
               | having to also have to inspect the three hand-rolled Tree
               | implementations, two of which are probably buggy, in
               | addition to the actual client code trying to use the tree
               | to do real work. Or a library that implemented it using
               | interface{} which is a big middle finger to the whole
               | type system, completely discarding the compiler's ability
               | to detect otherwise trivial mistakes.
        
             | fsdjkflsjfsoij wrote:
             | That depends entirely on the implementation. Almost every
             | respectable implementation strategy is going to have better
             | performance characteristics than an interface{} though.
        
       ___________________________________________________________________
       (page generated 2021-12-22 23:00 UTC)