[HN Gopher] Go Data Structures: Interfaces (2009)
       ___________________________________________________________________
        
       Go Data Structures: Interfaces (2009)
        
       Author : rednafi
       Score  : 94 points
       Date   : 2025-02-05 09:48 UTC (4 days ago)
        
 (HTM) web link (research.swtch.com)
 (TXT) w3m dump (research.swtch.com)
        
       | zknill wrote:
       | A quirk of Go is that I can cast a `[]string` to `interface{}`,
       | but I cannot cast `[]string` to `[]interface{}`. This blog post
       | is my go-to explanation for why the second is not possible but
       | the former is.
        
         | rednafi wrote:
         | I didn't know even know this. TIL, thanks.
        
         | saurik wrote:
         | Is that really a "quirk" of specifically Go? I am pretty sure
         | Java--the implementation of which defined a lot of how numerous
         | languages handle this kind of thing--has the same behavior: you
         | can cast String[] to Object, but not Object[].
        
           | kgeist wrote:
           | C# allows casting string[] to object[], but this requires the
           | compiler to insert runtime type checks each time the array
           | elements are modified:                 object[] array = new
           | string[10];         // throws a run-time exception
           | array[0] = 10;
        
             | neonsunset wrote:
             | Note that array covariance is largely considered a design
             | mistake and if .NET was to be redone it wouldn't have one
             | (and a bunch of smaller things that are an artifact of
             | generics getting introduced in C# 2.0 and not 1.0, well,
             | the problem is _much_ worse in Go heh).
             | 
             | Other data structures like List<T>, Span<T>, etc. do away
             | with covariance. There's an upcast for ReadOnlySpan<T> but
             | only because it's zero-cost and does not introduce the
             | issues stemming from covariance.
             | 
             | Luckily, you almost never see someone use array covariance
             | beyond occasional object[] upcasts (and the compiler is
             | also good at reasoning whether to insert covariance checks
             | or not).
        
               | pjmlp wrote:
               | Well, .NET only exists in first place because of Sun's
               | lawsuit, Ext-VOS was designed with J++ in mind.
               | 
               | Thus having high compatibility with how Java used to be
               | was a big factor designing C# 1.00
        
               | neonsunset wrote:
               | 25 years ago :)
        
               | pjmlp wrote:
               | Yes, and 25 years later Java is still relevant enough
               | that Microsoft has become yet again a Java vendor, with
               | their own distribution, made the key contribution for
               | Windows ARM support, VSCode Java experience is still
               | ahead of C# DevKit thanks collaboration with Red-Hat, and
               | doesn't require an additional licence.
               | 
               | At the same time, JVM has embraced the polyglot ecosystem
               | CLR was supposed to be, while C# seems to have sucked the
               | life of all those language implementations demoed at the
               | launch back in 2001, and .NET SDK being offered on
               | computer magazine CDs.
               | 
               | Which is kind of interesting, how things have changed 25
               | years later.
               | 
               | At least we finally have cross platform support (ignoring
               | Mono and DotGNU efforts), and good AOT instead of NGEN,
               | which should have been there on .NET 1.0.
        
               | neonsunset wrote:
               | If you have to reach for popularity as an argument in
               | favour of technology X or platform Y, perhaps it's not a
               | technical point you want to make?
               | 
               | > VSCode Java experience is still ahead of C# DevKit
               | thanks collaboration with Red-Hat, and doesn't require an
               | additional licence.
               | 
               | I'm not sure if you're intentionally attempting to make
               | inflammatory replies or something in what I said rubbed
               | you the wrong way.
               | 
               | (for other readers - this marks me posting for 20th time
               | here that DevKit is an optional product and thousands of
               | developers are happily coding in C# in VS Code and
               | VSCodium, Neovim and Emacs without ever running into it)
        
               | pjmlp wrote:
               | Unfortunately not always the best technology wins out,
               | and the turns that go around them are nonetheless quite
               | interesting.
               | 
               | I am stating facts, do you want links to .NET team
               | interviews where they assert it is a business decision
               | VSCode is never going to achieve feature parity with VS
               | for .NET?
               | 
               | VSCode for Java doesn't have such artificial constraint,
               | hence the better tooling, mainly implemented by Red-Hat.
               | 
               | C# DevKit being optional doesn't change the fact specific
               | features are only available when users opt into using it,
               | with a corrrespondig Visual Studio license.
               | 
               | And yes, this irritates me, because I feel it is a
               | disservice to .NET community how Linux and macOS
               | developers are kind of 2nd class, not by .NET team
               | themselves, but higher up Microsoft management.
        
               | neonsunset wrote:
               | No, I don't. Developer tooling teams which ship Visual
               | Studio and DevKit are almost completely separate from the
               | teams overseeing the development of .NET itself
               | (including the team responsible for the SDK). And,
               | honestly, I could not care less because it does not
               | affect me or my colleagues in any material way, nor it
               | affects the actual Linux or macOS users.
               | 
               | And also, have you tried writing Java in VS Code? I have
               | and it is overall worse (read: less stable) than just
               | using IntelliJ Idea in a way that isn't an issue when
               | doing so in C#/Go/Rust/TS.
        
               | pjmlp wrote:
               | Ah doesn't affect you that graphical profiling tools are
               | only available on VS, that management thought for a brief
               | second to make hot reload VS only (saved by .NET
               | community uproar and folks like Hanselman jumping onto
               | discussion?), or that MAUI designer was thrown into the
               | garbage can, and what is now available is pretty much
               | WIP?
               | 
               | Lucky one I guess.
               | 
               | I have used it, its stability issue is orthogonal to
               | feature parity, being whole Eclipse running headless,
               | which is the point, features.
        
               | kgeist wrote:
               | .NET is still called COM+ in .NET's source code, they
               | initially envisioned .NET as a successor to COM. They
               | wrote about COM+ in 1997 in an issue of Microsoft Systems
               | Journal (they talked about GC, runtime type info, etc.)
               | It makes me believe the idea for .NET (COM+) existed
               | before the lawsuit. It's possible that after the lawsuit,
               | the COM+ and J++ teams were merged to reposition it as
               | mainly a Java alternative.
        
               | pjmlp wrote:
               | That was the Ext-VOS project, that I made a reference to.
               | 
               | WinRT was supposed to replace .NET by the original goals,
               | after the Sinofsky and his followers took over Windows
               | development after Vista, it has been COM as the main API.
               | 
               | "Turning to the past to power Windows' future: An in-
               | depth look at WinRT"
               | 
               | https://arstechnica.com/features/2012/10/windows-8-and-
               | winrt...
               | 
               | However as we all know by know, it didn't went down as
               | expected.
        
               | kgeist wrote:
               | I never heard about "Ext-VOS". Tried googling it, and all
               | mentions of it are from you ("pjmlp"), on Reddit, Github,
               | etc. :) Where can I read more about it?
        
               | pjmlp wrote:
               | Don Syme from .NET generics and F# fame has written about
               | it, that is how we got made aware of it.
               | 
               | Now those blog posts are kind of gone from their original
               | hosts.
               | 
               | F# HOPL paper,
               | 
               | https://fsharp.org/history/hopl-final/hopl-fsharp.pdf
               | 
               | Original blog post on way back machine, including the
               | link to "MSR White Paper: Proposed Extensions to COM+ VOS
               | (Draft)" paper.
               | 
               | https://web.archive.org/web/20190111203733/https://blogs.
               | msd...
        
               | kgeist wrote:
               | OK, so from what I've read, "VOS" was the older name for
               | CTS (Common Type System), not the name of the whole
               | project. The project was named COM+, COM Object Runtime,
               | and Project Lightning at different times. "Ext-VOS"
               | ("extensions to VOS") is a document that primarily
               | proposes generics, which were only added in C# 2.0.
        
               | neonsunset wrote:
               | > .NET is still called COM+ in .NET's source code
               | 
               | There was mostly no reason to cause unnecessary code
               | churn to rename pre-existing code with complus-named
               | variables inside dotnet/runtime. External-facing features
               | and documentation never reference it. For compatibility
               | reasons it still recognizes env. variables prefixed with
               | COMPlus_ alongside DOTNET_ though.
               | 
               | See: https://github.com/search?q=repo%3Adotnet%2Fruntime%
               | 20complu...
               | 
               | In a similar vein, NativeAOT code still prominently
               | references project RedHawk (these are slowly being
               | renamed).
        
             | skitter wrote:
             | C# inherited this from Java, which added it because it was
             | designed without generics and therefore decided that the
             | additional expressiveness provided by enforced array
             | covariance was worth the tradeoff of having an unsound type
             | system.
        
           | sltkr wrote:
           | Actually in Java you _can_ cast String[] to Object[] although
           | this is technically unsound.
           | 
           | This how generic methods like Arrays.sort() are implemented.
        
             | saurik wrote:
             | Huh, fair enough!! What's then funny in the context of this
             | thread is that, while I am sure I knew that 20-25 years
             | ago, it feels so wrong that it would support that -- and
             | more like a "quirk" that it ever would -- that it surprises
             | me (though it certainly doesn't shock me: most things have
             | many quirks).
        
         | duskwuff wrote:
         | > I can cast a `[]string` to `interface{}`, but I cannot cast
         | `[]string` to `[]interface{}`
         | 
         | Surely that's just contravariance, though? You can't cast
         | []string to []any because that would allow you to write non-
         | strings to it.
        
           | saghm wrote:
           | Yeah, I have plenty of criticisms of Go, but this is
           | something that it got right.
           | 
           | For those who aren't familiar with the issue, in Java you can
           | assign an array of a subclass to a variable declared as an
           | array of the superclass, which leads to issues if you
           | actually try to mutate it. Imagine if Cat and Dog both
           | inherit from Animal, assigning a Dog[] to an Animal[] is
           | totally valid, but then setting one of the elements to a Cat
           | will throw an an exception.
        
             | nlitened wrote:
             | I can't help but keep noticing that all of the notorious
             | problems with OOP are not due to inheritance, but due to
             | mutability.
        
               | saghm wrote:
               | I don't know if I disagree with you or not, but having
               | not used a language with inheritance with a while, I also
               | don't really miss it at all. Even if it doesn't cause
               | problems on its own, it just doesn't feel like it
               | provides that much that can't be provided via some other
               | mechanism either.
               | 
               | For what it's worth, I'm not sure this is even something
               | I'd consider a "notorious" problem with OOP because the
               | solution is really simple and wouldn't break anything
               | else if done correctly from day one: just don't allow
               | using arrays of subtypes in a place where an array of its
               | supertype is expected. This was already known at the time
               | Java was designed, and I'm pretty sure the people who
               | designed Java knew it too; they just didn't pick the
               | right way to handle it in my opinion. There's nothing
               | inherent about OO that makes this bug that much harder to
               | deal with than any other language because it can happen
               | in any paradigm with subtyping (as demonstrated by the
               | fact that it could have been present in Go if they did
               | allow casting arrays of interfaces in that way).
        
               | zozbot234 wrote:
               | Interface inheritance is OK (as long as you pay attention
               | to these issues) but _implementation_ inheritance is a
               | huge mess because it 's effectively antithetical to
               | modularity. Your entire class hierarchy must be
               | understood/evolved as a whole if you use implementation
               | inheritance, that's the only way to avoid running into
               | severe problems with its semantics as the code changes
               | over time. Of course that's not exactly the most common
               | approach given that class hierarchies in many real-world
               | codebases are large and it's just not practical to survey
               | them as a whole.
        
               | PhilipRoman wrote:
               | The mutability is a red herring here. It depends on what
               | methods your object has and where the generic type
               | resides within the signature. It just so happens that an
               | immutable array has only one relevant "method":
               | get :: Int -> T
               | 
               | Here, T is on the right/return side, therefore an
               | immutable array type would be covariant. But a mutable
               | array also has a                   set :: Int, T -> void
               | 
               | which has T on the left/parameter side and therefore
               | requires contravariance (and you cannot have both,
               | therefore you get invariance). But the issue is not with
               | mutability, since you could just as well have something
               | like                   setImmutable :: Int, T -> Array(T)
               | 
               | and you still wouldn't be able to make Array(T)
               | covariant.
               | 
               | And for the record, it is only raw arrays in Java that
               | have this issue, generic types like List<T> are invariant
               | (of course you can explicitly ask for a List<? super T>
               | if you want to only call "setter" type methods on it, or
               | List<? extends T> for getters, aka methods where T
               | appears only on return side).
        
               | nlitened wrote:
               | This setImmutable would return a new instance of
               | Array<Animal> which would not be Array<Cat> like the
               | original, and would contain any mix of cats and dogs.
               | 
               | So immutability resolves the covariance problem, I think?
        
               | dwattttt wrote:
               | I've never quite understood enough about types to know
               | why, but: it's more like the difference between read-only
               | and write-only.
               | 
               | One of the variances is safe if you only read, and one is
               | safe if you only write.
        
               | PhilipRoman wrote:
               | My main point was that mutability and covariance are two
               | orthogonal concepts that just happen to overlap here due
               | to function signature shape. But it seems that you are
               | right, I cannot think of any expression that would type-
               | check with Array<Animal> but not with Array<Cat>. ChatGPT
               | was very unhelpful in trying to produce a counterexample,
               | since it probably trained on too many entry level
               | stackoverflow posts about this issue.
        
         | philippta wrote:
         | Because the latter would incur a hidden O(n) computation.
        
           | TheDong wrote:
           | since when has that stopped go? "[]rune(someString)" is O(n)
           | and quite inobvious
           | 
           | The O(n) loop is here: https://github.com/golang/go/blob/215d
           | e81513286c010951624243...
        
             | foldr wrote:
             | It's obvious that this is O(n), no? You're casting
             | something immutable to something mutable, so it has to be
             | copied.
        
               | debugnik wrote:
               | And decoding subsequences of UTF-8 code units (bytes)
               | into Unicode scalars (runes), which also forces a copy.
               | 
               | But why did Go pick the same syntax for cheap conversions
               | and expensive ones, though? I'd expect this to be a
               | standard function, not a type conversion.
        
               | foldr wrote:
               | Don't all conversions require copying the value at the
               | level of the language semantics? You can cast a float to
               | an int, but you can't assign an integer value to a float
               | variable (leaving aside whatever optimizations the
               | compiler might make). Casting a float to an int is only
               | cheap because a float is a fixed size. So it seems
               | unsurprising to me that casting a larger value results in
               | a proportionally larger copy.
        
           | rollcat wrote:
           | It's not just the O(n), you must think of interface{} as a
           | pair (concreteType, pointerToMutableData). It's a can of
           | worms, allocating n*2 word-sized objects for each rune is
           | just the surface layer - strings in particular are immutable.
           | 
           | I think Go does the right thing to make this allocation and
           | assignment explicit, you may be a little less surprised with
           | how the program actually behaves.
           | https://go.dev/play/p/PzuBpM66VX2
        
       | iTokio wrote:
       | If you are mindful of performance, one thing to be careful of, is
       | that interfaces often prevent inlining and can cause performance
       | issues.
       | 
       | Generally speaking the Go compiler seems to have trouble with
       | inlining, PGO (profile guided optimization) can help though.
        
         | indogwetrust wrote:
         | > Go compiler seems to have trouble with inlining
         | 
         | not "have trouble", a deliberate design to keep compile times
         | fast and (emitted) code size reasonable
        
           | the_gipsy wrote:
           | Ah yes, whichever it is, always design.
        
         | tapirl wrote:
         | Storing concrete values into interfaces often cause
         | allocations, which has a larger impact on performance than
         | inlining.
        
           | nasretdinov wrote:
           | Yeah I was really surprised by how much of a performance
           | difference it made when calling variadic functions that
           | support `args ...interface{}`. Literally every since call is
           | going to be an allocation of multiple parameters when passing
           | structs without a pointer
        
         | neonsunset wrote:
         | You have to manually collect and then apply the profile though
         | (very few teams do this). A proper solution is doing a whole
         | program view optimization (at least like the one done by .NET's
         | NativeAOT). But typically of Go design philosophy, a stop-gap
         | solution was chosen instead.
        
       | meander_water wrote:
       | The comparisons to python are a bit dated now. All of the
       | examples can be implemented in python using typing extensions [0]
       | and statically checked before runtime using mypy [1].
       | 
       | [0] https://docs.python.org/3/library/typing.html (from Python
       | 3.5 - released Sep 2015)
       | 
       | [1] https://github.com/python/mypy (v0.1 released Sep 2009)
        
         | rednafi wrote:
         | One thing I like about Go is that you can read a decade-old
         | blog and still find it somewhat relevant.
         | 
         | Python was the first language I made money with. However, these
         | days, I struggle to read and make sense of type-ridden,
         | generic-filled, Pydantic-infested Python code.
        
           | rollcat wrote:
           | > Python was the first language I made money with. However,
           | these days, I struggle to read and make sense of type-ridden,
           | generic-filled, Pydantic-infested Python code.
           | 
           | I'm in the same boat. Python was great when it was a snake. I
           | liked additions like the "with" statement - they were very
           | pythonic.
           | 
           | I think it's good when the language evolves, although the
           | direction Python took feels more like grafting - oh people
           | like cats, so let's graft some fur onto the snake; people
           | like bats so let's attach wings. The creature no longer
           | resembles a snake, or any other animal for that matter.
           | 
           | I still think Python 3.0 was the right thing to do - get rid
           | of "old style" classes, default to Unicode strings, be strict
           | about mixing Unicode with bytes, etc. While there, I wish we
           | got rid of the __init__(self, ...) crap - repeating yourself
           | three times was absurd. The language was getting better up
           | until 3.4 or so, and it slowly started going downhill from
           | there, async being the inflection point.
           | 
           | I don't think you can fix it anymore. Python 4.0 will never
           | happen, at least not the way 3.0 did.
           | 
           | Go has its problems too. "if err != nil" is awful - stack
           | unwinding exists, but it's awkward and "bad style". Tuples
           | exist, but not as a first-class object. Generics dropped way
           | too late (but at least I'm happy we went thru so many
           | proposals, finally settling on something actually
           | reasonable). Past mistakes cannot be easily undone, so I'm
           | happy it's taking a more conservative approach.
        
             | dotancohen wrote:
             | Please state you musings about the async. I'm just getting
             | into that right now and I wonder if my misgivings are a
             | result of my age or a fundamental issue with the Python
             | implementation of the concept.
             | 
             | Seriously, all and any insight and advice appreciated.
             | Thanks.
        
               | rednafi wrote:
               | If you're just getting into it, don't get derailed by the
               | Hacker News noise. It has its fair share of issues, but
               | many successful large-scale systems have been built with
               | it. I've worked on a few, and it's mostly fine.
               | 
               | I say this because when I picked it up, I was just as
               | confused by the hate, and it wasn't productive. It's just
               | another tool in your belt. Invest time in learning it,
               | then move on. Learn when to use it--and, more
               | importantly, when not to.
               | 
               | If you're curious about the issues with the sync/async
               | split, you might want to read Bob Nystrom's _What Color
               | Is Your Function?_ [1] blog post. But to highlight a few:
               | 
               | - Async is like a virus--it wants to make your entire
               | codebase async.
               | 
               | - Many workloads don't benefit from it; in fact, putting
               | things unnecessarily on an event loop slows them down.
               | 
               | - Running sync functions with a thread pool executor from
               | async is slow.
               | 
               | - The `asyncio` API is full of gotchas, and you need to
               | learn esoteric patterns to sidestep them.
               | 
               | - Edge case cancellation is tricky.
               | 
               | - There's a lack of proper structured concurrency
               | (`TaskGroup` and `trio.Nursery` attempt to fix this).
               | 
               | [1]: https://journal.stuffwithstuff.com/2015/02/01/what-
               | color-is-...
        
               | neonsunset wrote:
               | That Bob Nystrom's article is of uncharacteristically
               | poor quality. It's as if the author had very little idea
               | of tradeoffs between the API shapes that are favoured by
               | stackless and stackful coroutine designs. When I read it
               | in full in anger, I could not believe how shallow and
               | badly argued the conclusion was. Anyone citing it is
               | doing disservice to themselves and everyone around them.
               | 
               | Also note that Python's async implementation is one among
               | many. It is a terrible idea to extrapolate the
               | impressions about it onto different languages with
               | different implementations.
               | 
               | The statement "Python async is bad" is understandable but
               | there is a reason it got such a wide adoption after being
               | pioneered by F# and C#.
        
               | rednafi wrote:
               | The arrogance of the Hacker News randos never ceases to
               | amaze me.
               | 
               | Bob Nystrom worked on Dart at Google and wrote the
               | amazing _Crafting Interpreters_ book. Rob Pike referenced
               | this article in his 2023 talk, _Go: What We Got Right,
               | What We Got Wrong_ , while discussing the trade-offs
               | between CSP and coroutine-driven concurrency.
               | 
               | Python's async is okay, and I was involved in a few large
               | projects at one of those named companies. But the
               | abstraction could be better, and `asyncio` is a terrible
               | library. Trio fixed many of the problems with structured
               | concurrency, edge cancellation, and timeout issues.
               | 
               | But while it's not possible to rewrite every system, if I
               | needed to start a greenfield project where concurrency is
               | pivotal, Python wouldn't even be in my top three choices.
               | Go's CSP-style concurrency model is plain superior to
               | anything possible in Python. That's okay--Python has
               | other strengths.
        
               | neonsunset wrote:
               | > Bob Nystrom worked on Dart at Google and wrote the
               | amazing Crafting Interpreters book. Rob Pike referenced
               | this article in his 2023 talk, Go: What We Got Right,
               | What We Got Wrong, while discussing the trade-offs
               | between CSP and coroutine-driven concurrency.
               | 
               | I know, which is the exact reason the reply states "
               | _uncharacteristically_ ". And yet, it fails to
               | acknowledge existing terminology and tradeoffs and just
               | states at the end in a couple paragraphs how Goroutines
               | shall deliver us all from evil (which could not be
               | demonstrably further from the truth). Surely we need to
               | expect better from one of the Dart's authors than
               | forgetting to mention all the caveats virtual threading
               | via stackful coroutines comes with?
               | 
               | And this completely ignores that Go applications end up
               | having to reimplement .NET's task system system anyway
               | and that Goroutines are poorly suited for "fine-grained"
               | concurrency. Even the UX of Goroutines is bad. Want to
               | return a value when one exits? Be sure to not screw up
               | synchronizing and passing the data by hand. One must
               | imagine Gophers writing all that boilerplate happy. I
               | suppose, it's a preferred pastime to learning the
               | internals and ceasing to post the same tired shibboleths.
        
               | rednafi wrote:
               | Who is talking about .NET here?
               | 
               | .NET is out of the question when candidates flat-out
               | refuse to work with GoF-style OO. No one claims to be
               | excited about building modern distributed systems in
               | Microsoft's Java.
               | 
               | That said, the claim that goroutines are "poorly suited
               | for fine-grained concurrency" is misleading. Define fine-
               | grained.
               | 
               | Goroutines have near-zero startup cost and can scale to
               | millions, whereas .NET's async-await is still backed by a
               | thread pool with scheduling overhead. The real issue
               | isn't execution but synchronization, and Go provides both
               | message passing (chan) and low-level synchronization
               | (sync.Mutex, atomic). If you're writing workloads where
               | every nanosecond counts, you're probably using
               | specialized primitives anyway--not .NET Tasks.
               | 
               | As for UX--want a return value? Use a channel. Need to
               | sync? Use sync.WaitGroup. Need structured cancellation?
               | context.Context. The alternative in .NET involves
               | juggling Task.WhenAny(), ConfigureAwait(false), and state
               | machine overhead--as if keeping all that junk in your
               | head doesn't come at a cost.
               | 
               | Goroutine orchestration isn't "boilerplate"--it just
               | gives you explicit control instead of forcing you into
               | .NET's awkward, OO-ridden async/await scaffolding.
               | 
               | But yeah, let's rewrite Docker in .NET.
        
               | neonsunset wrote:
               | > .NET is out of the question when candidates flat-out
               | refuse to work with GoF-style OO.
               | 
               | If you think that it's all there is to .NET (besides your
               | account, I have not encountered anyone seriously using
               | the term in a long time), then I'm sorry you had to
               | experience that specific codebase or a team environment
               | that made you think this way. Pattern abuse and "GoF" is
               | a Java-ism and comes from not having sufficiently
               | expressive language together with particular culture of
               | application architecture. However, please, stop repeating
               | the same phrase in every message and actually look into
               | what differentiates C#, Go, Java, etc. Could be a
               | pleasant and illuminating learning experience.
               | 
               | And unlike Go, C# is a multi-paradigm language which is
               | effective at functional and systems programming :)
               | 
               | > Define fine-grained.
               | 
               | Take a transport, like an HTTP client, send two/three/n
               | requests concurrently. Or fire off two relatively quickly
               | completing but still benefitting from multi-threading
               | computations.
               | 
               | > Goroutines have near-zero startup cost and can scale to
               | millions
               | 
               | This perception is the problem. Their cost is not near-
               | zero. There is no free lunch:
               | https://hez2010.github.io/async-runtimes-
               | benchmarks-2024/tak...
               | 
               | > As for UX--want a return value? Use a channel. Need to
               | sync? Use sync.WaitGroup. Need structured cancellation?
               | context.Context. The alternative in .NET involves
               | juggling Task.WhenAny(), ConfigureAwait(false), and state
               | machine overhead--as if keeping all that junk in your
               | head doesn't come at a cost. Goroutine orchestration
               | isn't "boilerplate"--it just gives you explicit control
               | instead of forcing you into .NET's awkward, OO-ridden
               | async/await scaffolding.
               | 
               | Do you actually have any experience with this or are you
               | simply repeating something that you _think_ is a problem?
               | Context propagation issues are known and the resulting UX
               | has a common complaint of being unwieldy in Go.
               | CancellationToken or even implicit cancellation
               | propagation in F# are far superior and long-time solved
               | problems. I guess, in the land of Go, if Go does not
               | provide, it must be impossible.
               | 
               | And why would anyone need to bother with explicit
               | synchronization anway? That's what 'await' is for. Have
               | more than one task? Just fire them off, and 'await' when
               | you do need a result. No Task.WhenAny/All etc. necessary.
               | These are for more complicated logic, or for mapping a
               | sequence/collection of tasks onto results. Very handy and
               | terse. Turns 25-line mess in Go into a sleek one-liner.
        
               | patates wrote:
               | The context problem is really annoying when using go.
               | Many functions unnecessarily adding a context parameter
               | so they can cancel a task. They made such nice syntax
               | sugar for channels, why not also context?
        
               | yndoendo wrote:
               | Never used F#. Looked it up for thread cancellation and
               | it is syntactic sugar similar to defer in Go. Kind of
               | like an await return is a wrapper around f(x) callback.
               | 
               | Been years since I wrote Go code, until last week. Daily
               | has become C#, python, and powershell. Took a day to fall
               | back in and write a tool while learning a new GUI
               | framework. Found the strengths and weaknesses of the GUI.
               | 
               | Personal, I find Go more pleasurable to work with than
               | python and C#. Reality, the project requirements dictate
               | the languages that maybe used. This is the reason why Go
               | was chosen over the others listed and unlisted C, C++,
               | Swift, rust, ...
               | 
               | Which would be a better experience with developing for
               | iOS, Swift or C# or Go? If it was company's main solution
               | or product, Swift, even when I will need to learn it.
               | 
               | >Take a transport, like an HTTP client, send two/three/n
               | requests concurrently
               | 
               | That is business logic. Back end maybe embedded and this
               | could become a DOS attack.
               | 
               | .NET implemented the code to manage task pools so you
               | don't have to. N aysnc is really a set of Z handlers.
               | With syntax sugar.
               | 
               | >Context propagation issues are known
               | 
               | Doesn't a context need to exist for two entities to
               | communicate and no system can exist where context is not
               | shared?
               | 
               | This is the adapter pattern in GoF, which in can be
               | reduced to function g controlling the interactions
               | between function a and b, where function a or b may act
               | upon the agreed upon return type, callback type, or
               | memory type. Address of the return, callback, and memory
               | must be shared either with-in g() or a() and b() .. from
               | CPU register up to system memory address.
               | 
               | >And why would anyone need to bother with explicit
               | synchronization anway?
               | 
               | Dependent on business logic. Including which 3rd party
               | libraries must be used for the solution. Software might
               | be architecture for using Reactive objects which mimic
               | async / await with more well defined and shared behavior.
               | A custom managed thread maybe needed for more accurate
               | time limits or an event loop is needed.
               | 
               | >sleek one-liner.
               | 
               | Be warned, one liners may seem useful, until they need to
               | be debugged. There is a great difference between the two
               | C# Reactive objects:
               | 
               | {                  doing.Where(x =>
               | x.CanDo()).Subscribe(x => x.ExtensionDigestion());
               | doing.Where(x => {
               | 
               | #if DEBUG // Set ID_WITH_ISSUES_OR_TESTING to the proper
               | ID when developing new user experience or interface. Use
               | for debugging single issue bug that just happens with the
               | same ID or same line of 3rd party product tie-ins.
               | if (x.Id == ID_WITH_ISSUES_OR_TESTING) {
               | var i = DateTime.UtcNow();                }
               | 
               | #endif                          return x.CanDo()        }
               | .Subscribe(x => {                x.ExtensionDigestion();
               | });     }
        
               | neonsunset wrote:
               | > Doesn't a context need to exist for two entities to
               | communicate and no system can exist where context is not
               | shared?
               | 
               | Please read the comment carefully. This refers
               | https://pkg.go.dev/context in Go.
               | 
               | > That is business logic. Back end maybe embedded and
               | this could become a DOS attack.
               | 
               | It is not. There is nothing in lightweight concurrency
               | that makes it "business logic"-specific.
               | 
               | > Be warned, one liners may seem useful, until they need
               | to be debugged.
               | 
               | This has nothing to do with reactive patterns below.
               | Task.WhenAll debugs just fine, and if one or multiple
               | tasks throw, you get a nice AggregateException which can
               | be further inspected with specific stack traces and such.
               | 
               | > Dependent on business logic. Including which 3rd party
               | libraries must be used for the solution. Software might
               | be architecture for using Reactive objects which mimic
               | async / await with more well defined and shared behavior.
               | A custom managed thread maybe needed for more accurate
               | time limits or an event loop is needed.
               | 
               | Again. This is a reply to the comment about how in order
               | to yield a value out of a goroutine, you need to manually
               | fashion a place where to store it, and then to create a
               | WaitGroup or a Cond/Mutex or maybe a channel and then
               | manually wait on it. Something you get with a single
               | 'await' keyword in C#. It has nothing to do with business
               | logic or spawning an OS thread, or using a reactive
               | framework.
        
               | coder543 wrote:
               | tl;dr Async/await is what the Python community has
               | settled on, so I don't think there's any point in
               | resisting it now, but the fact that it happened is a
               | historical curiosity from my point of view.
               | 
               | -----
               | 
               | I'm not the person you replied to, and I actually don't
               | have anything against async/await as a pattern where it
               | is needed, but I know that prior to Python getting
               | async/await, there were some moderately popular green
               | threading / coroutine libraries like gevent and eventlet.
               | You would (mostly) just write normal, synchronous Python,
               | and then blocking calls would be intercepted by the
               | runtime and allow other coroutines to take their turn.
               | This felt Pythonic to me at the time, because most code
               | would work in both sync and async environments. You
               | didn't have to write a separate async version, and you
               | didn't really have to update old libraries.
               | 
               | The other pre-async/await approach was taken by Tornado
               | and Twisted... basically a form of callback hell. I don't
               | think anyone liked this, but it might have been popular
               | because it worked... and _unofficial_ green threading
               | implementations like gevent /eventlet sometimes broke in
               | interesting ways. (I think _officially_ incorporating a
               | gevent /eventlet-style solution into Python would have
               | overcome most of the issues... but, that's just my
               | speculation.)
               | 
               | I've never used Python professionally outside of some
               | short scripts, but it was one of the first languages I
               | used seriously for hobby stuff back during the early days
               | of the Python 3 transition. I never fully understood why
               | Python chose to switch to async/await. Promises can be
               | useful for structured concurrency patterns, but as
               | someone who has been writing Go professionally for a
               | number of years... I just don't think most code should
               | _need_ to be async-aware.
               | 
               | For a language like Rust, I think async/await makes
               | perfect sense. Rust cannot afford to impose a runtime on
               | everyone, and async/await can be implemented in a very
               | low level, efficient way that gives the developer as much
               | control as they need. This kind of ultra-low-level
               | optimization stuff just isn't relevant to Python... so,
               | as an outsider, I almost wonder how (in Python)
               | async/await _isn't_ just a clunkier coroutine system.
               | 
               | If I were to try to rebut my own comment, I would say
               | that async/await was probably chosen because "explicit is
               | better than implicit", and green threading might have
               | been too implicit for the Python community's tastes.
        
               | rednafi wrote:
               | There are a few truths in this. I've been involved in a
               | few large projects written in Python and have seen both
               | ups and downs.
               | 
               | Green threads required extensive monkey patching, and
               | debugging those programs was incredibly hard. Instagram
               | moved from them to async/await and wrote a blog post
               | about it, iirc.
               | 
               | But I agree that Python's async/await implementation is a
               | bit too low-level and could use better abstractions. A
               | lot of the hate Python async gets is due to the `asyncio`
               | library. It's a shame that the default library is full of
               | deprecated and gotcha-ridden APIs. Trio attempted to fix
               | these, but adoption has been low.
               | 
               | The community settled on async for the same reason I love
               | Go despite all its faults. It's flawed, but you can build
               | successful systems with it. Lots of companies still write
               | new services in async Python instead of Go because, as
               | big as the Go community is, Python's is absolutely
               | ginormous.
               | 
               | Plus, LLMs brought more new people to Python than most
               | other languages, and it's easier to find Python
               | developers and teach them async than to hire Gophers.
        
               | rollcat wrote:
               | Here's a good one:
               | https://journal.stuffwithstuff.com/2015/02/01/what-color-
               | is-...
               | 
               | The problem is futures (async/await) as a concept;
               | JavaScript, Rust, etc all have the same problems.
               | 
               | Take CSP by contrast: https://en.wikipedia.org/wiki/Commu
               | nicating_sequential_proce...
               | 
               | Any Go function can take a channel, return a channel, or
               | use a channel (and spawn goroutines) internally,
               | unbeknownst to the caller/callee. It _may_ lead to some
               | bad design decisions (code that must not be async may
               | never be), but it won 't get in your way when you least
               | need a refactor.
        
             | rednafi wrote:
             | This is a more elaborate version of how I feel about Python
             | these days. Python is still a much nicer language than
             | something like JS, but it hasn't been very successful at
             | saying no to things.
             | 
             | I like type hints, but it's easy to go overboard with them.
             | Pydantic and FastAPI are great. The problem is that
             | typenauts and academics coming from other languages are
             | trying to bring every feature under the sun to Python. The
             | core team hasn't been able to fight this barrage of feature
             | requests.
             | 
             | The same is true for Go. I regularly see Rust/Haskell folks
             | talking about how things could be better if Go had xyz
             | feature. While it's true that Go would probably have
             | benefited from a little more expressiveness, how much more?
             | Where do you stop?
             | 
             | I like Go because it's not Rust or Zig. I mostly write
             | server software, and Go is far more productive in that
             | space. The Go team understands this and is much more
             | protective about scope creep. Keep your type theory off my
             | lawn and let me make money in peace, please.
        
             | dfawcus wrote:
             | Re tuples, I guess you mean the multiple return values, and
             | this assignment form: https://go.dev/play/p/vxm6lV7JS4z
             | 
             | However how would real first class tuples be an improvement
             | in Go? Alef had them, and allowed various manipulations, as
             | well as returning them, and passing them to functions.
             | 
             | I note that they are present in Hare, but not present in
             | Odin. Where the latter has the Go inspired multiple return
             | values, but (AFAICS) no tuples, but does add tagged unions.
             | 
             | Generally I'd not want to store a tuple, preferring a
             | struct with named fields.
             | 
             | So the only uses I can think of are those temporary ones
             | for multiple return values and assignments, which are
             | already covered.
        
               | rollcat wrote:
               | My issue with Go's implicit tuples is similar to the
               | pre-1.18 generics built into the language. We've had
               | generic append since before 1.0, but it was "magical",
               | you couldn't write your own generic append for e.g. a
               | custom container. We've later managed to add generics in
               | a way that didn't make append, make, etc seem out of
               | place; but make remains a function with an optional,
               | _typed_ second parameter, IIRC the only one of its kind.
               | 
               | The implicit tuples seem just as magical. You can have
               | func f()(int, error), but a:=f() is an error. It's
               | arguably better than Lua (which ignores the second
               | value), but arguably loses to Python (which returns a
               | proper, first-class tuple).
               | 
               | Similar with destructuring. You can have g()
               | struct{int;error}, but not i,err:=g() or struct{i, err}
               | := g(). You can have f()(int, error), but again not
               | a:=f(). You _can_ have h(int, error) with h(f()), but
               | that 's a hardcoded special case, and somewhat
               | unintuitive, since it violates x:=f(); h(x) - which would
               | however hold in case of returning a struct. Go is just
               | less composable, full of arbitrary exceptions and edge
               | cases.
               | 
               | (I do still love it though.)
        
               | dfawcus wrote:
               | Sorry, but your response seems to be about consistency,
               | or "purity" at some level.
               | 
               | While what Go has may be inconsistent, what functional
               | impact does that have?
               | 
               | I can't see a need for 'de-structuring' as such, absent
               | tuples. Even if it had real first class tuple types, like
               | Alef did, what would one do with them? As I indicated,
               | I'd not want to store them (other than holding in
               | locals), prior to use.
               | 
               | As I recall, Alef did support de-structuring with tuples,
               | as well as re-structuring. One could assign either way
               | between an unnamed tuple, and an 'aggr' (it's name for a
               | struct).
               | 
               | So at most I'd want to break them apart, which the return
               | value thing gives.
               | 
               | Hence if I was creating Go 2.0, I can't see why I'd want
               | to add first class tuples, but could see a use for adding
               | tagged unions.
        
               | rollcat wrote:
               | > Sorry, but your response seems to be about consistency,
               | or "purity" at some level.
               | 
               | > While what Go has may be inconsistent, what functional
               | impact does that have?
               | 
               | Same reasons why Go fixed C's: inside-out type
               | declarations, function pointer syntax, ERRNO, headers,
               | macros, signal handling, UB, all the things that
               | technically had no "functional" impact but still directly
               | contributed to consistency, ergonomics, clarity, ease of
               | comprehension, and (either by proxy or directly)
               | correctness.
               | 
               | > I can't see a need for 'de-structuring' as such, absent
               | tuples.
               | 
               | Your playground example of a, b = b, a is not
               | destructuring a tuple in action? It's basically the same
               | syntax / mechanism as Python's destructuring assignment,
               | which existed since before Go (except Python's was always
               | more powerful).
               | 
               | It's almost like you can do everything you want with a
               | tuple in Go, except for actually holding it in your hand.
               | 
               | > Even if it had real first class tuple types, like Alef
               | did, what would one do with them?
               | 
               | Similar things you'd do with a function without a name -
               | work directly with the data at hand, without having to do
               | the extra round trip to the attic to declare its name or
               | shape.
               | 
               | > Hence if I was creating Go 2.0, I can't see why I'd
               | want to add first class tuples, but could see a use for
               | adding tagged unions.
               | 
               | That would probably break Go. I liked Chris Siebenmann's
               | take on the subject:
               | 
               | https://utcc.utoronto.ca/~cks/space/blog/programming/GoUn
               | ion... https://utcc.utoronto.ca/~cks/space/blog/programmi
               | ng/GoUnion... https://utcc.utoronto.ca/~cks/space/blog/pr
               | ogramming/GoUnion...
               | 
               | Meanwhile tagged unions bring you virtually all the way
               | to ADTs, where pattern matching (generalised
               | destructuring) is basically a must.
               | 
               | (By the way, Python stumbled really badly when it added
               | pattern matching without even having proper structs. It's
               | almost comical, given def __init__(self, ...), that
               | should've been gone as a part of the 3.0 break-the-
               | world.)
        
               | dfawcus wrote:
               | > Similar things you'd do with a function without a name
               | - work directly with the data at hand, without having to
               | do the extra round trip to the attic to declare its name
               | or shape.
               | 
               | Note that in Alef, tuples are essentially a dual for an
               | aggr, but with unnamed fields. So one always has to
               | (explicitly, or implicitly via inference) declare its
               | 'shape', in terms of number of members, and type of
               | members.
               | 
               | So one could declare:                   tuple (int, byte
               | *, int) t;
               | 
               | Then manipulate 't', one could also have a function
               | return a tuple as in:                   tuple (int, byte
               | *, int) something(int x) { /* ... */ }
               | 
               | Then handle its return value either as:
               | t = something(2);
               | 
               | or                   byte *str; int value;         (nil,
               | str, value) = something(7);
               | 
               | However the tuple 'shape' is always statically
               | determined. Is that in your view satisfactory, or not?
               | 
               | Or do you desires something where the tuple is an
               | entirely dynamic type, sort of akin to syntax sugar on
               | top of '[]interface{}'? More akin to the sort of dynamic
               | thing which Python offers?
               | 
               | Such that one can potentially have a program run, and
               | each call to a given function returning a tuple may have
               | different numbers of elements, potentially of different
               | types within it. So that for said program, if the
               | function return value depended upon input data, one could
               | not determine the full set of tuples which may be
               | returned?
        
             | throawayh666 wrote:
             | Wow, we're on pretty much the same path.
             | 
             | Loved Python when I got into it circa 2011. Didnt have
             | prior programming experience, minus basic BASIC and HTML.
             | It was simple enough and the stdlib include enough things
             | to get me going, but it had enough complexity to intrigue
             | me to dive deeper (list comprehensions, bytes vs strings,
             | inheritance vs composition) and I think I learned a ton
             | about programming thanks to it.
             | 
             | But these days when I see modern Python, it looks
             | "uncomfortable". It has so many features, and so many ways
             | of doing things that just figuring that out feels like a
             | massive time sink.
             | 
             | I write server-side software with Go now. I feel like with
             | Go I can just sit down and start solving problems. That
             | applies even to sitting down and diving into a 10 year old
             | codebase.
        
         | globular-toast wrote:
         | More specifically, you can do interfaces with Protocol[0]. The
         | main problem with it seems to be as soon as you a single
         | Protocol mypy becomes extremely slow.
         | 
         | [0]
         | https://docs.python.org/3/library/typing.html#typing.Protoco...
        
       | andreyvit wrote:
       | @dang Probably needs (2009)?
        
       ___________________________________________________________________
       (page generated 2025-02-09 23:01 UTC)