[HN Gopher] Go is not an easy language
___________________________________________________________________
Go is not an easy language
Author : jen20
Score : 439 points
Date : 2021-02-22 03:57 UTC (19 hours ago)
(HTM) web link (www.arp242.net)
(TXT) w3m dump (www.arp242.net)
| unscaled wrote:
| I think Go can only be defined as "simple" in the New Jersey
| (worse is better) sense of simple, that is: "it is more important
| for the implementation to be simple than the interface."
|
| A lot of things about Go are not simple. In my opinion a simple
| language is not about how simple it is to write a parser or a
| compiler for that language, but it's about how many different
| non-trivial and arbitrary pieces of information the developer has
| to memorize.
|
| This is highly tied to the Principle of Least Astonishment[1] in
| language design: how many unexpected surprises does the
| programmer have to deal with?
|
| With Go, you get quite a lot:
|
| 1. Go already _has_ generic types. These are the magical maps,
| slices and channels. Everything else is not.
|
| 2. Even if you think #1 was also true for Arrays in Java 1.4 and
| no one was complaining, Go goes further: it already has generic
| functions like 'copy', 'len', 'min' and 'append'. Since you
| cannot properly describe the interface of a magic built-in
| function like 'append' using the Go language itself, this is not
| a standard library function, but should be viewed as an entirely
| new piece of custom syntax, like the print statement in Python
| 2.x.
|
| 3. Nil interfaces and interfaces with a nil pointer are not
| equal.
|
| 4. Multiple return values are a magical beast - they are not
| tuples and you cannot manipulate them in any useful way.
|
| 5. Channel axioms[2]. Possibly one of the more astonishing and
| painful aspects of Go.
|
| 5. Slices are mutable, unless you copy them. This can lead to
| some very surprising cases where a slice is passed down many
| layers below and then modified, breaking the caller.
|
| 6. Continuing the topic above, Go has neither clear data
| ownership rules (like Rust), clear documentation tradition on who
| owns the data passed to functions (like C/C++) nor a way to
| enforce immutability/constness (like C++, Rust or FP languages).
| This really pushes a lot of the cognitive overload to the
| developer.
|
| 7. Go modules are a lot better than what we had before, but are
| quite hard to deal with. The moment you need to move to v2 and
| above and start creating subdirectories they becomes rather
| confusing compared to what you would do in other package
| management system.
|
| 8. If a simple language is a language that allows you to write
| _simple programs_, and you follow Rich Hickey's classic
| definition of Simple[3], then Go is probably one of the LEAST
| simple languages available today.
|
| tl;dr: I'm not saying other languages often compared to Go (like
| Rust or Java) don't have their own share of complexities, but I
| don't think Go should be viewed as a simple language in the
| broadest sense. It is a language with a rather simple
| implementation for our day and age (though Pascal was much
| simpler if we stretch this definition backwards).
|
| [1] https://wiki.c2.com/?PrincipleOfLeastAstonishment [2]
| https://dave.cheney.net/2014/03/19/channel-axioms [3]
| https://www.infoq.com/presentations/Simple-Made-Easy/
| harikb wrote:
| Going by their first example, the fact that I couldn't write
| list.delete(value)
|
| without realizing it involves a linear search is considered one
| of the benefits of Go.
|
| That said, I agree Go could use a bit for ergonomic and handy
| methods, while still remaining efficient.
|
| The example on Concurrency also has a different answer. Go gives
| you all the tools, but there are many different ways their
| problem can be solved. By explicitly avoiding a "join" method and
| having any default 'channels', Go makes all these possible. What
| is missing is some of the popular patterns that emerged in the
| last few years need to make it to into stdlib or some cookbooks.
|
| 1. If you want a worker model, I would start n=3 go-routines and
| they all receive from a single channel. They don't need to much
| around with a channel of buffer 3, as in the example.
|
| 2. If the workers already return data from a compute, the read
| from driver serves as the wait.
|
| 3. In other cases, there is sync.Waitgroup available to
| synchronize completion status.
|
| 4. End of work from the driver can be indicated via a channel
| close. Closed channels can still be read from until they are
| empty and the reader can even detect end-of-channel.
|
| Designing concurrent system is a bit complicated. Some tutorials
| do make it sound like a `go` keyword is all you need. All of
| these can fixed by improving std-lib or cookbooks.
| prepperdev wrote:
| Yes. That very example shows why itis hard to write fast
| program in Ruby. It's not even the interpreter (which is slow),
| but it's that Ruby is pure magic.
|
| I was never able to understand what happens, when I looked at a
| particular snippet of Ruby code, if it was not me who wrote it.
| With Go, understanding others people code is a trivial task.
| d3nj4l wrote:
| What part of list.delete(x)
|
| Is hard to understand if you didn't write it?
| philosopher1234 wrote:
| I don't think that example tells the whole story. You have
| to zoom out and consider what code based look like when
| this trade off is made repeatedly by devs. When "easyness"
| or DRYness becomes king you get ravioli code, and it
| becomes unintelligible. Keep it simple
| majewsky wrote:
| My persistent impression of the Ruby ecosystem is that
| everyone optimizes for clever one-liners so hard that all
| other priorities go out the window.
| nobleach wrote:
| As much as I don't personally enjoy writing Go. This is a
| huge benefit. There's a decent amount of conformity around
| the "right way" of doing things. I'll admit, it has
| definitely made me consider my approach to solving problems
| in other languages much simpler. I'm not back to writing
| loops in JavaScript when I use it. It really bugs the
| functional programming folks but... no one can argue that
| it's readable, and it's pretty fast.
| chinmaythosar wrote:
| Linear search if it's not sorted. If it's sorted binary search
| will be much faster. So you need a flag for the function to
| tell it to use one of the two. This is why writing generic
| functions can be tricky. Programmers may use something
| inefficient just because it makes their lives easier.
| jtsiskin wrote:
| Binary search won't be any faster (in big O) because it's
| backed by an array: you'll still need to copy every element
| after the removed one from slot n to n-1.
| harikb wrote:
| Does Ruby track that a list is already sorted? If not, you
| are still adding cognitive overhead for the programmer to
| track it
| vinger wrote:
| You can sort by many things. Knowing where you sort
| (usually as far up the chain as possible) is important
| z0r wrote:
| linear search can be faster than binary search for small
| arrays, depending on your processor architecture.
|
| writing generic functions is difficult, so it's nice if a
| language allows people to do so, otherwise you get N
| inefficient and/or buggy reimplementations of the function in
| every project needing it. (not sure if that is your point)
| hderms wrote:
| obligatory link: https://www.evanjones.ca/linear-
| search.html
| chinmaythosar wrote:
| my point was that the author uses an example like search
| and called go not an easy language. so i was trying to give
| an example where it's not necessary a linear search or
| search is trivial ...
| anonymoushn wrote:
| If your developers hand write a binary search to delete
| an element you are extremely likely to end up with bugs.
| So it would be nice if there was a generic binary search
| too.
| BenFrantzDale wrote:
| That Go doesn't have the equivalent of `std::lower_bound`
| is pretty ridiculous.
| arp242 wrote:
| Linear search is also quite a bit faster than maps for
| small arrays (in Go at least, last time I tested it).
| d3nj4l wrote:
| The advantage here isn't that it's ergonomic, it's that if I
| get an "unknown" array I have no special knowledge about, I can
| simply use list.delete instead of having to write a linear
| delete and sleep soundly in the knowledge that it'll have well-
| known performance characteristics for that case. If I know
| something special about an array, I'm still free to write my
| own deletion algorithm, and nobody would fault you for that.
| joelfolksy wrote:
| Since simpler languages are easier to work with, let's just
| remove goroutines and channels from Go. Then all these problems
| will just go away.
| [deleted]
| technics256 wrote:
| I'm just learning Go myself, where can I learn or reference
| these popular patterns that have emerged over the past few
| years?
| harikb wrote:
| What comes handy is this [1] link, but I will update if I get
| any better links or others might chime in.
|
| [1] https://blog.golang.org/pipelines
|
| It is a very long doc, but that also shows that concurrency
| has so many patterns one might like.
|
| My own pattern is typically
|
| 1. Decide level of parallelism ahead of time and start
| workers (that many `go X()` invocations 2. Setup
| sync.Waitgroup for the same count 3. Create two channels, one
| for each direction. 4. Job itself needs some sort of struct
| to hold details
|
| Most of my jobs don't support mid-work cancelation, so I
| don't bother with anything else.
| rakoo wrote:
| Your 1. and 2. are merged in the almost-stdlib package
| errgroup: https://pkg.go.dev/golang.org/x/sync/errgroup.
|
| It also uses context (useful for long running, concurrent
| jobs) and handles mid-work cancellation
| majewsky wrote:
| Study the generic reader/writer implementations in the io
| module. (On my system, those sources are in
| /usr/lib/go/src/io.) The io.Reader and io.Writer interfaces
| are very simple, but very powerful because of how they allow
| composition. A shell pipeline like `cat somefile.dat | base64
| -d | gzip -d | jq .` can be quite directly translated into
| chained io.Readers and io.Writers.
|
| Another example of this is how HTTP middlewares chain
| together, see for example all the middlewares in
| https://github.com/gorilla/handlers. All of these exhibit one
| particular quality of idiomatic Go code: a preference for
| composition over inheritance.
|
| Another quality of idiomatic Go code is that concurrent
| algorithms prefer channels over locking mechanisms (unless
| the performance penalty of using channels is too severe). I
| don't have immediate examples coming to mind on this one
| though, since the use of channels and mutexes tends to be
| quite intertwined with the algorithm in question.
| wisswazz wrote:
| I really like http://tmrts.com/go-patterns/
| majewsky wrote:
| This looks like some C++ or Java developer trying to
| shoehorn OOP concepts into a language that is not OOP-
| first. Just to pick out one particularly egregious example:
|
| > Registry: Keep track of all subclasses of a given class
|
| Go does not have classes.
| arp242 wrote:
| > Going by their first example, the fact that I couldn't write
| list.delete(value) without realizing it involves a linear
| search is considered one of the benefits of Go.
|
| I think this is mostly okay. Most experienced programmers will
| realize this is a linear search and that it may be slow on
| large arrays. And turns out that in the overwhelming majority
| of the cases that's just fine!
|
| For other cases it's not-so-fine, but the mere presence of
| "list.delete" doesn't really stand in the way of implementing
| another, more efficient, solution.
|
| Overall, I certainly think it's _better_ than implementing the
| same linear searches all the time yourself!
| [deleted]
| alkonaut wrote:
| And it's not exactly gatekeeping to think that anyone who
| uses a method on any data structure on their platform should
| know the (rough) complexity of it (At least in the family
| constant, linear, or better/worse than linear). Removing a
| value from an array-backed list that isn't at the end is
| usually not a good idea.
|
| My main problem with having "remove items by value" in
| standard library apis is that it assumes a notion of value
| equality. Having that notion inserted at the very "bottom"
| (Like Equals/GetHashCode in .NET for example) is a mistake
| that has caused an infinite amount of tears.
|
| I much prefer this situation where the user must provide his
| own implementation and think about equality. It's
| boilerplate, but the boilerplate is useful here.
| lstamour wrote:
| > I much prefer this situation where the user must provide
| his own implementation and think about equality.
|
| But that doesn't scale beyond the first user. Every
| subsequent developer now needs to read implementations to
| understand what the code does thanks to a lack of
| standardization for these functions. Consider if there was
| a smaller standard library with fewer interfaces and
| conventions, it would become a lot harder to understand a
| number of concepts in Go, by design. That's fine, but
| conventions are what made Ruby on Rails projects so
| successful that they scaled up to being the monolith
| monstrosities most of us with startup experience ended up
| knowing them to be.
|
| Note that I'm suggesting something akin to C++'s standard
| library where algorithms are already written for you to use
| and compose with. Yes, the drawbacks are a slower compile
| time, and some conventions like constexpr can really
| complicate things, but... I can't say that a larger
| standard library or a larger set of conventions would make
| Go harder to use assuming the implementations hide a
| sufficient amount of complexity to outweigh the overhead of
| learning of them in the first place.
|
| What functions provide more value than the overhead
| required to learn them? Delete is one such function,
| mutable immutable data structures generally are likely
| another. Yes, the documentation needs to specify big O
| complexity, but it can still be easy to read. For example:
| https://immutable-js.github.io/immutable-js/docs/#/List
|
| The only way to get easier than that would be to use the
| same syntax for immutable operations as mutable ones:
| https://immerjs.github.io/immer/docs/introduction
|
| I recognize that the Go community finds the built-in
| standard library restrictive now, but that's no reason not
| to support a versioned standard library that ships
| separately but contains common functionality. I can only
| point to TypeScript for how such a system might work, given
| the large community project that exists to provide and
| publish types for that language, without actually
| publishing them with the language or compiler itself,
| excluding browser dom etc.
| alkonaut wrote:
| > But that doesn't scale beyond the first user. Every
| subsequent developer now needs to read implementations to
| understand what the code does thanks to a lack of
| standardization for these functions.
|
| There is no standardization. It's a hidden piece of logic
| that developers slowly and painfully understand.
|
| If I you _have_ to pass an equality function when
| removing an item from a list by value (or equivalently
| when creating a set or dictionary) it would always be
| explicit. That doesn't mean it can't be standardized. A
| framework can provide typical implementations (and often
| does!) such as "ReferenceEquals" or
| "StringComparison.Ordinal" etc.
|
| Another unfortunate side effect of the "equality as a
| property of the type" is that you can only have one such
| equality. And if you are passed a Set of dogs you still
| can't know for sure whether the equality used is actually
| the one declared in the Dog type of at Set creation (or
| possibly in the Animal base class). It's a mess. And it's
| so easy to avoid - the default virtual equality is simply
| not necessary.
| lstamour wrote:
| This is why I tend to like the example shown by the
| Immutable javascript libraries. They implement find-by-
| value using a library-specific calculation of equality
| built on primitives, but if you need something custom,
| you can pass predicate functions to, for example, perform
| your own equality check.
|
| I think we're agreeing here, despite the initial
| disagreement. Standards don't have to solve every edge
| case, nor do they have to integrate with existing
| language patterns such as "equality as a property of the
| type" though a standard function could have multiple
| variants. The Go way of doing this might be like how
| regular expression function names are built.
|
| Also, the library doesn't _have_ to implement delete that
| way. If functional programming and predicate functions
| are an encouraged design pattern, you could replace
| delete-by-value functions with a filter function which
| could clearly indicate what happens if more than one
| value is found as well as how the equality check is
| performed but glosses over using slices to update the
| array. Some might say it 's slower
| https://medium.com/@habibridho/here-is-why-no-one-write-
| gene... but it doesn't have to be slower, it's just
| tradeoffs in how the go compiler and language currently
| works vs could work.
| simiones wrote:
| Why would introducing value equality of all things be a
| problem? I think the opposite is true: many languages force
| you to write error-prone boilerplate because they lack a
| good definition of value equality built into the language
| and ecosystem.
|
| Go in particular is worse on this front than any language
| more high-level than C. It defines equality for a very
| limited subset of built-in types, with no way to extend
| that notion of equality to anything that is not covered;
| nor any way to override the default equality assumptions.
| This makes it extremely painful whenever you want to do
| something even slightly advanced, such as creating a map
| with a struct as key when that struct has a pointer field .
|
| And since pointers have lots of overloaded uses in Go, this
| turns a potentially small optimization (remember this field
| by pointer to avoid creating a copy) to a mammoth rewrite
| (we need to touch all code which was storing these as map
| keys).
| kaba0 wrote:
| I'm sorry for the "gatekeeping", but do you really want to
| work together with someone that doesn't know the language's
| standard library or won't even look at the documentation? And
| instead it would be positive that he/she writes the very
| implementation of something?
| jholloway7 wrote:
| This problem transcends documentation of any given
| language's standard library. list.delete(value) in any
| programming language is a degenerate without an ordering or
| hash built-in to the underlying data structure.
|
| If you ever need to delete based on value, it's a smell
| that a list is the wrong data structure. I'm sure there are
| cases in constrained environments where a linear search is
| heuristically okay, but generally in application
| development list.delete(value) is a hint that you're using
| the wrong data structure.
| mhaymo wrote:
| Good point. I guess the reason the author finds removing
| from lists to be relevant is because there is no Set
| structure in the Go standard library. They should use a
| `map[t]bool`, but that is non-obvious to many
| programmers.
| vlowther wrote:
| `map[t]struct{}` saves you a few bytes per entry. Just
| use the `_,found := foo[key]` form of lookup
| nitrix wrote:
| map[t]struct{} so that the values don't require any
| space.
| dullcrisp wrote:
| My Google search suggests that maps in Go aren't ordered.
| If so this doesn't work in place of an array.
| simiones wrote:
| Unfortunately, map[t]bool (or map[t]struct{}) only works
| for special structs that happen to have equality defined
| for them. It's not a general purpose solution, it's just
| a hack.
| citrin_ru wrote:
| > Most experienced programmers will realize this is a linear
| search and that it may be slow on large arrays.
|
| Experienced programmer can think that values in a list with
| such operation are indexed by a hash and delete by value is
| O(1) unless there is a big warning in documentation. Novice
| programmer probably haven't seen such combination of an array
| and a hash table and will assume O(N) as taught in a college.
| wizzwizz4 wrote:
| Experienced programmers will know that such a thing is
| possible, but I've never seen any (non-JavaScript, non-PHP)
| language where primitive arrays involve a hash table.
| Gaelan wrote:
| And even in JS and PHP (and Lua) where arrays and hash
| tables are technically the same data structure, a hash
| table used as an array isn't actually indexed on
| elements, just numbers, so you don't get O(1) search.
| shakow wrote:
| Experienced programmer will know whether the vanilla array
| of the language they use feature this kind of convoluted
| data structure.
|
| Which, for virtually all languages, is "obviously not".
| thezilch wrote:
| Experienced programmers, if perf matters to their project,
| should think about their data structures, memory size, and
| access patterns. It's not clear why an experienced
| programmer would think an array defaults to wasting memory
| on a hash table. Why use an array, if they need lots of
| values and to delete them at random?
| eweise wrote:
| Developers in other languages know the performance
| characteristics of list.delete because there is only one
| implementation throughout 99% of their codebase. Go you have to
| look at the implementation every single time to make sure its
| doing the right thing.
| johnisgood wrote:
| Haskell documentation uses the Big O notation everywhere
| which I really love. I wish Go and many other languages did
| the same.
|
| An example would be https://hackage.haskell.org/package/conta
| iners-0.4.0.0/docs/.... size :: Map k a ->
| IntSource O(1). The number of elements in the map.
| member :: Ord k => k -> Map k a -> BoolSource O(log n).
| Is the key a member of the map? See also notMember.
| lookup :: Ord k => k -> Map k a -> Maybe aSource O(log
| n). Lookup the value at a key in the map.
|
| And so forth...
| johannes1234321 wrote:
| In the C++ standard the complexity of algorithms is a
| requirement for a compliant implementation. Many text books
| skip it, but better documentation references it. Taking the
| "delete element from list" example from this thread
| https://en.cppreference.com/w/cpp/container/list/remove
| states that there is a linear search.
| mhh__ wrote:
| Andrei Alexandrescu has a scheme to encode these in the D
| typesystem on his website. It didn't get merged into the
| standard library in the end but you can happily do it in
| your own code
| arp242 wrote:
| Redis does the same; e.g. [1]:
|
| > Time complexity: O(N) where N is the number of elements
| to traverse to get to the element at index. This makes
| asking for the first or the last element of the list O(1).
|
| I agree this is something more documentations should do
| when possible; it doesn't even have to be big-O notation as
| far as I'm concerned, just a "this will iterate over all
| keys" will be fine.
|
| Ruby's delete() doesn't mention any of this, although you
| can easily see the (C) implementation in the
| documentation[2]. In principle at least, this doesn't
| _have_ to be O(n) if the underlying implementation would be
| a hash for example, which of course has its own downsides
| but something like PHP may actually do this with their
| arrays as they 're kind of a mixed data structure? Not
| sure.
|
| [1]: from https://redis.io/commands/lindex
|
| [2]: https://ruby-doc.org/core-3.0.0/Array.html#method-i-
| delete
| Groxx wrote:
| Redis's documentation is _wonderful_ , I wish every
| system I use were documented even half as well.
| btilly wrote:
| My experience is that developers in other languages have no
| idea of the performance characteristics of list.delete. It
| seems low-level and therefore obviously fast.
| mdemare wrote:
| "Fancy algorithms are slow when n is small, and n is
| usually small." Rob Pike, creator of Go.
| saagarjha wrote:
| Not at the scale of his employer.
| andi999 wrote:
| But isn't usually when using lists performance is dead
| anyway (due to cache misses).
| simiones wrote:
| "List" is usually a generic term for any data structure
| that can hold many items, and that has an API for adding
| or removing items. This can be implemented using an
| array, a linked-list, a hasthable, or a more advanced
| data structure.
|
| Also, in languages with GCs, linked lists do not
| necessarily cause more cache misses than arrays, due to
| the way allocation works (this is especially true if you
| have a compacting GC, but bump-pointer allocation often
| makes it work even when you don't).
| pansa2 wrote:
| Linked-lists, maybe, but `list`s in Python are just
| resizable arrays.
| kindall wrote:
| resizable arrays of pointers to objects stored elsewhere,
| which is going to blow through your cache anyway,
| especially when you start accessing those objects'
| attributes. so it's more a memory management strategy
| than a performance optimization.
| pansa2 wrote:
| But when removing an item it's not necessary to visit
| every element, just re-arrange the pointers (and in
| CPython, modify the reference count of the item that was
| removed).
| sokoloff wrote:
| When removing an item _by value_ (being the use case in
| this thread), you do need to visit the elements to check
| the value.
| [deleted]
| jholloway7 wrote:
| You can write a list data structure with performant 'delete'
| properties if you're willing to maintain a sort or a hash
| table. There would be people here bitching about memory usage
| if the stdlib did that natively. Here's a solution: don't use
| list.delete. You're using the wrong data structure if that's
| your solution to whatever collection you're maintaining.
| jholloway7 wrote:
| Downvotes on comp sci 102. Nice.
| code_duck wrote:
| You'd probably do better on HN to substitute 'decrying'
| for 'bitching'.
| jholloway7 wrote:
| Noted, thanks. Didn't realize that was the issue.
| mirekrusin wrote:
| We have this saying in my country "if grandma had a
| mustache she'd be a grandpa".
| omaranto wrote:
| I like my country's version better: "if grandma had
| wheels she'd be a bicycle".
| jholloway7 wrote:
| If your aunt had nuts she'd be your uncle
| omaranto wrote:
| That has the same basic idea as the mustache version, but
| is pithier. I still prefer the surrealism of the bicycle.
| dmitriid wrote:
| > What is missing is some of the popular patterns that emerged
| in the last few years
|
| Erlang has had these "popular patterns" for decades. And all
| languages that ignore them, making devs reimplement them,
| poorly, with third-party libs and ad-hoc solutions.
| jnwatson wrote:
| It has been said that Go4 software design patterns are signs
| of language defects.
|
| Most all of them can be implemented as single lines of code
| in Python.
| koenigdavidmj wrote:
| Most languages codify common idioms from earlier ones; I
| don't see how most of the patterns are any different.
|
| I'm sure there were a lot of assembly programs that used
| the "call stack pattern". I'm sure there are a lot of C
| programs that abuse struct packing to do inheritance (the
| "class pattern", sometimes with crappy vtables) or the
| preprocessor to do "templates". And even in Python, you've
| got to use the visitor pattern due to single dispatch.
| moonchild wrote:
| > without realizing it involves a linear search is considered
| one of the benefits of Go.
|
| The primary problem that I think the author was trying to get
| at is that go _lacks tools for building abstractions_. In a
| language with generic functions (for instance), it would be
| possible to implement a delete_at function completely in user
| code. The fact that you cannot reflects poorly on the language.
|
| Of course it will always be possible to write functions that
| have arbitrarily slow time complexities. That's something you
| have to be aware of, with any function; it's not a reason to
| avoid including a linear-time function to remove an element at
| a given index of an array.
| grey-area wrote:
| That's a deliberate choice in many ways, and it means you
| don't end up building castles in the air, or worse, working
| within someone else's castle in the air. See the lack of
| inheritance for a good example of a missing abstraction which
| improves the language.
|
| Abstractions are pretty and distracting but they are not
| _why_ we write code and each one has a cost, which is often
| not apparent when introduced.
|
| That's not to say Go is perfect, there are a few little
| abstractions it would IMO be improved by having and the slice
| handling in particular could be more elegant and based on
| extending slice types or a slices package rather than built
| in generic functions.
| stiray wrote:
| > and it means you don't end up building castles in the
| air, or worse, working within someone else's castle in the
| air. See the lack of inheritance for a good example of a
| missing abstraction which improves the language.
|
| Very well said. If only more developers would understand
| this.
|
| Yes I know, creating interfaces, inheriting everything from
| the void, adding as large amount of layers to create the
| beautifully constructed reusable structure is a nice drive
| but it might happen no one will reuse it as doesn't want to
| dive itself trough infinite level lasagna with rolling
| ravioli around it (and - thanks god, go doesn't have
| try/catch - throwing exceptions trough all the layers to
| force you to go down the guts of the construct to figure
| out what it means). Or, as it often happens, will be forced
| to reuse it, complicating his life and is code.
|
| I do understand that inheritance, even operator overloading
| has its meaning and is extremely useful. But it has another
| side - everybody are overdoing it as "might come handy
| later" and the code becomes a case for "How to write
| unmaintainable code"[2].
|
| When I am coding I am not doing it for philosophical
| reasons and go has until now succeeding being a very
| helpful tool without much of "meaning of life"
| complications. And i would love to see it stay that way.
|
| If you are forcing me to read the documentation / code
| (presumably I know what I am trying to solve) to be able to
| use the "beautiful oo construct" and forcing you to read
| your beautiful design, you have failed making it and I have
| seen it in java everywhere. I just hope the same people
| making everything complicated more than it need to be wont
| skip from java[1] train to go train. I really don't want
| them anywhere close.
|
| [1]https://github.com/EnterpriseQualityCoding/FizzBuzzEnter
| pris...
|
| [2]https://github.com/Droogans/unmaintainable-code (and 20
| others)
| marcus_holmes wrote:
| Yeah, this. I always found myself creating beautiful OO
| constructs that were invariably overfitted to my
| understanding of the state of the problem at that time,
| and were really hard work to change once I understood the
| problem better (or it changed).
| doctor_eval wrote:
| Yeah I've fallen into this trap and worst of all, I've
| had my team fall into it and not find their way out.
|
| Abstractions are a great idea, and Go has plenty of these
| (io.Writer is ubiquitous), but most business logic is
| dead boring and doesn't need it.
| kaba0 wrote:
| I don't see why a well-built standard library would be in
| the way. In the case of delete, well write your own linear
| search if you are into, though it gets old imo fast. But
| will you also write a sort algorithm by hand? Because I am
| fairly sure it won't be competitive with a properly written
| one. Also, what about function composition? Will you write
| a complicated for loop with ifs and whatnot when a filter,
| foldl and friends would be much more readable?
| cube2222 wrote:
| Go actually does contain a sort package in the stdlib:
| https://golang.org/pkg/sort/ with the most ergonomic
| function being sort.Slice
|
| As for a complicated for loop instead of the functional
| equivalent: I too am a fan of functional patterns in this
| case, but the for loop equivalent in my opinion is just
| as readable in 99% of cases. A little more verbose,
| that's all. Matter of taste.
| somehow1 wrote:
| Here is an example, https://www.cryptopp.com/ , I love
| this library. Beautifully designed, extremely capable,
| incredible what you can do in two lines of code. I hate
| to use it down to my guts. Try to use it and you will
| understand.
| Gibbon1 wrote:
| Seems to me there are a lot of different types of
| collections which all have a delete operation. Feels like
| go forcing you to hand roll a delete forces you to
| violate the principal that a bit of code should care as
| little as possible about things it's not about.
|
| If code breaks because list is now a hashmap, that seems
| like an anti-feature.
| hmage wrote:
| > the principal that a bit of code should care as little
| as possible about things it's not about
|
| Can you find/quote where that principle comes from? I'm
| genuinely curious.
| kaba0 wrote:
| I think it is mostly based on OOP paradigm, like
| principle of least knowledge.
| Gibbon1 wrote:
| It's a motivation for things like OOP, Generics, and
| macro's.
| grey-area wrote:
| I agree filtering and many generic operations on slices
| would be nice, I think they have plans for it once
| generics lands. I don't agree they are huge problems
| though, they are much simpler than sorting algorithms for
| example (which are included).
|
| A range vs a series of filtering operations is an
| interesting question in terms of which is easiest to
| write or, crucially, for a beginner to understand after
| writing - not convinced foldl is easy for beginners. If
| you like abstractions like that, Go will be abhorrent to
| you, and that's ok.
|
| It is easy to build your own filter on a specific type,
| I've only done it a couple of times, it is literally no
| more than a for loop/range. Where I have written
| extensions and would appreciate helpers in the std lib is
| things like lists of ints and strings - contains, filter
| and friends would be welcome there in the stdlib (I use
| my own at present).
|
| For filter etc I think I'd use them but am not convinced
| they would change the code much - the complexity is in
| what you do to filter not the filtering operation itself
| which probably adds about 3 lines and is very simple (set
| up new list, add to list in range, return list). For
| example in existing code I have lots of operations on
| lists of items which sit behind a function and for hardly
| any of them would I delete the function and just use
| filter in place at the call site, because I have the
| function to hide the complexity of the filtering itself
| (all those if conditions), which would not go away. I
| wouldn't even bother rewriting the range to use filter
| because it's not much clearer.
| systemvoltage wrote:
| I want compiled Python with strong types. All the batteries of
| Python included. And runs at C speed. Go feels like C and Python
| had a baby except the baby looks more like C and less like
| Python.
| mberning wrote:
| Many people just don't understand the joy of working with
| something like python or ruby. Maybe they haven't had an
| immersive experience with an "a ha!" moment. Coming from basic
| and similar languages to java, then later to jruby and ruby,
| and now back to java I have to say it depresses me how much
| more I enjoy some languages and technologies over others. I
| wish I could feel equally excited and happy to work in them.
| systemvoltage wrote:
| 100%, being productive is addicting and fun. Laboring through
| for-loops 80th time in a the day is something for sadist
| purists, not me.
|
| As a non-expert at Go, I would have loved classic OOP +
| batteries included Python and remove the curly braces.
| Perfect.
| pid_0 wrote:
| This might be wrong but can't you use cython and then compile
| python to speed it up? Obviously won't be as fast as Go but
| seems like it can have speed ups
| cb321 wrote:
| If you think of Cython as a "new but related" language rather
| than just a Py compiler then you can sprinkle enough C-like
| `cdef` code into your Cython so that, when compiled, your
| code can run much faster than Go..as fast as any C, really.
| (EDIT: Pypy is usually a better way to make unadorned Python
| run faster.)
|
| That said Nim [1] provides nicer metaprogramming features
| than Cython and very nice overall ergonomics..I think more
| ergonomic than Python with, e.g. UFCS. Nim has almost all the
| safety/speed of Rust with better ergonomics than Python. What
| is not to love?
|
| [1] https://nim-lang.org/
| pid_0 wrote:
| I'll definitely check out nim. The best thing about python
| is the ecosystem and vast knowledge available. It makes it
| hard to move to something "better"
| cb321 wrote:
| The Nim Forum and IRC channel are pretty helpful/rapid
| feedback. Maybe not as much as a canned StackOverflow
| answer for Python or an existing library to do exactly
| what you need, but the Nim ecosystem/userbase is growing
| all the time.
| statenjason wrote:
| Have you looked at Nim? Python-like syntax, compiles to C, and
| a considerably sized stdlib (http, parsing, channels)
|
| https://nim-lang.org/
| hombre_fatal wrote:
| Nim is an impressive undertaking, though I was sheepishly
| overwhelmed when I tried using it for a quick project and
| realized I might have to spend 30 more minutes reading the
| docs than writing cowboy code. It's quite a big language:
| https://nim-lang.org/docs/manual.html
|
| Would love to take a more serious look later.
| peheje wrote:
| I can recommend a look at Crystal also, although Crystal is
| not even 1.0
|
| Between the two I found I needed much less referencing with
| Crystal, and when I did, I found it more intuitive, well
| described and easier to remember and apply the same
| concepts in other places.
|
| https://crystal-lang.org/reference/index.html
| j-james wrote:
| You may enjoy working with Nim.
|
| Its main drawback is the small number of native libraries
| available for it, but wrapping and using existing libraries
| from other languages is a walk in the park.
|
| https://nim-lang.org/
| systemvoltage wrote:
| Thanks, Nim looks extremely interesting. Anyone here used Nim
| in production?
| cb321 wrote:
| Also, I am not sure what counts as "in production" but on
| literally a daily basis I use about 15 different command
| line utilities I wrote in Nim using cligen [1] (EDIT:
| Really I have written several dozen but only about 15..20
| are useful every day). There are always compiler
| bugs/things to workaround (as with any language), but Nim
| has been pretty usable for years.
|
| [1] https://github.com/c-blake/cligen
| cb321 wrote:
| The Nim Forum [1] is written in Nim and has been used in
| prod for years. A commercial video game Reel Valley was
| written in Nim. Status [2] is doing Ethereum 2.0 stuff with
| it.
|
| [1] https://forum.nim-lang.org/ [2] https://status.im/
| zhobbs wrote:
| Swift could be interesting for you. Modern syntax, easy to use,
| performant.
| d3nj4l wrote:
| If you're ok with s/python/ruby Crystal is great, LLVM compiled
| and has a couple of cool web frameworks coming up.
|
| https://crystal-lang.org/
| viraptor wrote:
| It won't be ready for production use for a while, but
| https://github.com/python/mypy/tree/master/mypyc is being
| worked on.
| lmm wrote:
| I stopped missing Python once I found Scala. It may not quite
| be C speed, but it's fast enough (orders of magnitude faster
| than Python, good enough for doing casual linear algebra in).
| Standard library is probably smaller than Python's, but you
| don't need it because the JVM world has really good dependency
| management (Maven).
| jpgvm wrote:
| Kotlin pretty much did this for me. I'm no longer interested
| in writing Python or Go because Kotlin is better than both in
| every dimension that matters to me personally. i.e
| power/expressiveness, performance and runtime/ecosystem
| quality.
| lmm wrote:
| Heh yeah fair enough. I find Kotlin infuriating to work in
| (far more so than many objectively worse languages) because
| it's such a step backwards from Scala, but if I didn't know
| what I was missing then I'd probably feel the same way.
| jpgvm wrote:
| I actually used Scala before Kotlin. I get what you mean
| in Kotlin being less powerful than Scala but for me this
| has turned out to be a good thing in bigger projects.
| Kotlin provides enough power to get the job done without
| giving people too much rope to hang themselves with.
| lmm wrote:
| For me it's not about power, it's about consistency.
| Scala has a couple of general concepts that get applied
| to everything. Kotlin seems to have a new ad-hoc way of
| doing every different thing. E.g. my teammate just
| rewrote some code to use a Scala-style IO type rather
| than suspend functions because none of us could
| understand which coroutine context was being used where.
| bobbylarrybobby wrote:
| Mypy with python's new static types is a fantastic replacement
| for "real" types. The types mostly don't exist at runtime
| (except for some rare cases like how dataclass handles ClassVar
| variables) but they'll take you very far during development
| time, especially paired with a language server like Microsoft's
| Pylance.
| tengbretson wrote:
| This is all just crazy to me. I cannot even remember the last
| time I dealt with array indices directly.
| nitinreddy88 wrote:
| In my opinion, coming from Java,C# background and currently
| working on golang, here's my take.
|
| 1. Golang did right design in terms of OOP
|
| 2. They didnt complicate the language by accepting everything.
|
| 3. At the same time, they made simple things so complex. Simple
| logics to removing an element from slice/array and even iterating
| them without screwing up the data.
|
| I have been programming for 10yrs and I find myself doing those
| mistakes without proper IDE/golint, while I can easily code in
| other languages without much help. I can concentrate on solving
| the business problem rather than fiddling around the language
| ironmagma wrote:
| "Everything should be made as simple as possible, but no
| simpler." I've often felt Go went too far trying to make
| everything simple, to the point that it's wrapped around and
| made things complex.
| avmich wrote:
| "Civilization advances by extending the number of important
| operations which we can perform without thinking of them."
| --Alfred North Whitehead
|
| Go forces writing loop for removing an element from an array
| even if the programmer knows it's linear time. Ruby gambles
| that programmer isn't ignorant - or it's not critical - but
| allows the convenience every time.
|
| Designing languages is hard.
| cratermoon wrote:
| > Go forces writing loop for removing an element from an
| array
|
| It does not:
|
| copy(a[i:], a[i+1:]) // Shift a[i+1:] left one index.
|
| a[len(a)-1] = "" // Erase last element (write zero value).
|
| a = a[:len(a)-1]
| millstone wrote:
| Your code removes by index. This can be done in one line
| in Go: list = append(list[:i],
| list[i+1:]...)
|
| but to remove by value you need a loop.
| pansa2 wrote:
| It's crazy that the function you call to remove an
| element from a list is `append`.
| pests wrote:
| If you come at it with the thought that you are creating
| two sublists (on each side, avoiding the index) you then
| need to merge them back together. That append reads to
| just recreate the list from the two parts.
| ziml77 wrote:
| This makes it very unclear what's going on. I had to test
| it to find that this specific use is special cased to not
| copy. And very surprisingly you don't even have to assign
| back to the same variable to get this no-copy behavior!
|
| Edit: I didn't do my test quite right. It's not really
| special-cased. But it's still very surprising to see this
| happen:
|
| Code: s1 := []int{1, 2, 3, 10, 11, 12}
| s2 := []int{4, 5, 6} s3 := s1[:2] s4 :=
| append(s3[:2], s2...) fmt.Println(s1)
| fmt.Println(s4)
|
| Output: [1 2 4 5 6 12] [1 2 4 5
| 6]
| millstone wrote:
| This is a Go idiom, one of the "slice tricks" that you
| are expected to just know. In fairness every language has
| its non-obvious idioms.
|
| It may or may not copy.
| Groxx wrote:
| Yup.
|
| Simple! But not easy. Go is absolutely filled with
| nuggets like this in my experience. False-simplicity is
| deeply ingrained in the standard library as well:
| https://fasterthanli.me/articles/i-want-off-mr-golangs-
| wild-...
| mrmonkeyman wrote:
| Just write the damn loop. This magic bullshit is very un-
| Go.
| c-cube wrote:
| When I see that, I have no idea if it allocates a new
| array or if it's in place. Which one is it? At least with
| a delete function it's clear.
| millstone wrote:
| In Go it may or may not allocate a new array; it depends
| on the array's capacity. This means that the resulting
| slice may or may not alias the original slice.
|
| See this playground for an illustration:
| https://play.golang.org/p/mOpuGVj2ypG
|
| That uses append to delete, and then modifies element 0
| in the result. In the first call, only the new slice is
| modified. But in the second call, both slices are
| modified, since they share the same backing array.
|
| I consider this to be one of the worst mistakes of Go, to
| design a highly multithreaded language in which variable
| aliasing is a _dynamic_ property. I am convinced that
| many Go programs have latent races and bugs, invisible to
| the race detector, and they have not yet been tripped
| because they have been (un)lucky with the runtime 's
| capacity decisions.
| z0r wrote:
| you are right that a loop isn't required, but the point
| that kind of index / array element juggling is a headache
| still stands
| Aachen wrote:
| Having worked a lot with at least a dozen languages, this
| is definitely on the cumbersome side of the spectrum. And
| a sibling comment says it doesn't even do what it says on
| the tin. In what other language does it take two devs to
| remove an element from an array?
| gameswithgo wrote:
| A lot of the things in Go that seem horrible seem simple if you
| are used to the slice syntax used in Python and other langs,
| which I am not.
| cratermoon wrote:
| removing an element from a slice is trivial if you don't care
| about order. Just swap the element you want to remove with the
| last element and truncate the slice. If you want to maintain
| order, shift all the elements following the one you want to
| remove left one and then truncate. The latter can be slow if
| the slice is large, but it's linear time.
| systemvoltage wrote:
| OP isn't asking for a solution. OP is talking about not
| having to implement the solution themselves and having the
| language provide some shortcuts for doing what you just
| described.
| cratermoon wrote:
| shortcuts for trivial operations is how we got the leftpad
| disaster.
| gizmo686 wrote:
| Leftpad was a result of making libraries (comically)
| small. If you have a bunch of convenience operations, you
| can put them all together into a single utility library
| (or even the standard library).
| justin_oaks wrote:
| People need those shortcuts so they don't reimplement
| them over and over. Not having the leftpad as part of the
| language or standard library is what caused the problem.
| This is exactly the problem the article talks about.
| oftenwrong wrote:
| >Not having the leftpad as part of the language or
| standard library is what caused the problem.
|
| There are a few other comments in this tree making the
| same claim - is this what most people took away from the
| left-pad incident?
|
| To me, the stdlib lacking left-pad is a fairly distant
| cause. You could make the same case for any popular
| library, but that road leads to a bloated stdlib, which
| forever will haunt us. The more relevant causes, in my
| book, are:
|
| 1. Many builds relied on pulling left-pad directly from
| NPM
|
| 2. NPM allowed left-pad to be unpublished
|
| I like to compare this to the usual practice in the Java
| world:
|
| 1. The primary repository in use is the Maven Central
| Repository. Nobody can unpublish a library from it. This
| is specifically to avoid pulling dependencies out from
| under consumers of those dependencies.
|
| 2. Most Java shops run their own repository. This serves
| both to caches requests to the Maven Central Repository,
| and to provide a fail-safe in case something were to
| happen to Maven Central. Backing up dependencies is
| considered as important as backing up your own code.
| Sometimes vendoring is used as an alternative approach.
| dagmx wrote:
| If the leftpad functionality was part of JS by default it
| wouldn't have happened.
| throwaway189262 wrote:
| Leftpad was a result of the JS standard library being
| terrible and not providing built-ins for such simple,
| common things.
|
| And Go is the same way...
| cletus wrote:
| I'm a little surprised deleting an element from a list is this
| hard but that seems to be a case. That seems like an API
| oversight. You shouldn't be writing your own code for what is a
| fundamental operation.
|
| As for leaking Goroutines, yep that's a real thing. I had
| colleagues working on a Go project where exactly this was
| exhausting memory and causing the process to be killed. It can
| happen really easily.
|
| I view this as a fairly fundamental problem with GC languages. GC
| solves some problems of course but in doing so it creates other
| problems.
|
| For request-servicing type problems like servicing HTTP requests,
| I actually think the PHP/Hack model is pretty much ideal:
| stateless core (so low initialization costs unlike, say, Python),
| cooperative multitasking (ie no threads per se) and just throwing
| everything away after you're done. But I digress...
|
| What Go does have going for it is syntax. It's minimal,
| opinionated without being too opinionated (IMHO) and familiar to
| anyone who has ever used a C-style language.
|
| I tend to view Go as a better Python.
| jlouis wrote:
| Deleting an element from a slice is a O(n) operation. It better
| be explicit in the program so the programmer knows just how
| expensive it is. Either you know what you are doing, and then
| writing it down is trivial, or you don't know what you are
| doing, and you are creating an efficiency problem for the
| future. Other data structures have a delete operation because
| it is efficient on those.
|
| And fully agree on the GC part. GC doesn't remove the work of
| resource management. It simplifies it vastly. This can some
| times lull people into thinking they can ignore resources
| altogether.
| cletus wrote:
| Yeah, I'm on board with the idea that expensive or not-
| recommended operations should be more verbose to discourage
| them.
|
| But you'll find other expensive operations (eg prepending an
| element to a slice) are much less verbose. At a certain point
| (of verbosity), you're just adding the potential for bugs.
| damagednoob wrote:
| > Deleting an element from a slice is a O(n) operation. It
| better be explicit in the program so the programmer knows
| just how expensive it is.
|
| The chances of deleting an element in an array being a
| bottle-neck are really low. I'd rather save my brain cycles
| for thinking about disk/network access and wait for profiling
| to show me the problem.
| s3cur3 wrote:
| Agreed. And the fact that disk access might end up being a
| bottleneck is not a good argument for making disk access
| APIs less ergonomic. I say give the programmer the nicest
| set of tools possible and let them make informed decisions
| about what's appropriate to use when.
| skybrian wrote:
| But why does the strings package exist if it's better to
| write out the for loop each time? Do you think there won't be
| a slices package after Go gets generics?
|
| The argument that writing out the loop is better for
| readability is just status quo bias. The real reason we don't
| have standard functions for such things is a technical
| limitation that will probably be removed soon.
| otter-in-a-suit wrote:
| Another thing that drove me up the wall recently (semi-related
| to leaking goroutines) are libraries that doesn't use the
| context package or a done/quit channel and can get stuck in
| long-running operations that hug absurd amounts of CPU/memory.
|
| As far as I'm aware, you simply cannot kill a goroutine from
| another goroutine, i.e. there is no terminate()/stop()
| construct, meaning you cannot implement a timeout on top of an
| existing library without actually modifying said library.
| 02020202 wrote:
| Go's simplicity forces you to write exact code and not use
| "magic" methods. It makes the code very readable and easy to
| understand right off the bet even if you have never worked with
| the project. Go IS easy, it's just on occasion a bit verbose due
| to the lack of generics or "magic" methods. That does not mean it
| is hard.
| mybrid wrote:
| Simple and easy are two words with no meaning. Why? Because
| simple and easy attest to common brain paths that do not exist.
| In computer science the divide between functional programming
| ease and simplicity as with the likes of Lisp and procedural
| languages as with like Python stand in stark contrast. Functional
| programmers believe everyone thinks like they do even though the
| vast majority of software is procedural and whose population do
| not find being lost in sets of parenthesis easy or simple. Easy
| and simple are meaningless because there is no uniform, blank
| slate learning path in the human brain. In fact, if one had to
| model every person having a unique learning path versus today's
| notion that what's easy from me is easy for thee then the unique
| model would surely realize far more efficacy in practice.
| zimbatm wrote:
| It depends what "easy" means. Go is a language that has very few
| surprises.
|
| As a beginner, it takes a while to figure out good goroutine
| design patterns. And you get bit by append to slice copies a few
| times. And it takes a bit of time to figure out how to structure
| code efficiently, and get over the fact that the code is not
| completely DRY.
|
| I'm sure I forgot a few gotchas, but this list is still a miles
| shorter than all the popular languages out there.
| thayne wrote:
| As someone without a ton of experience with Go, a good amount the
| Go code I have encountered "in the wild", has actually been more
| difficult to read and understand than code in more complicated
| languages because I have to read through and understand all of
| these patterns. Hopefully the addition of generics will help with
| that. But IMO the simplicity of go actually hinders readability.
| fileeditview wrote:
| My personal experience is very different. Of course I have seen
| bad Go code in the ~5 years I do Go development professionally.
|
| But when compared to previous monstrosities in C++ or Java with
| exceptions cluttered everywhere, deep inheritance trees that
| are absolutely useless..
|
| then Go code is an absolute breeze to read and work with. The
| one thing I see frequently and dislike a lot in Go code bases
| is the frequent usage of interfaces for single structs just for
| the sake of mocking for unit tests.
|
| Often I see cases where you could just strip the whole layer of
| crap and unit test the real functions themselves. But nobody
| seems to think about that. It seems that this "write interfaces
| for everything and then mock/test this" pattern is dominating
| currently.
| thayne wrote:
| > But when compared to previous monstrosities in C++ or Java
|
| Perhaps part of it is the languages we are comparing to? I'm
| comparing to languages such as rust and scala, and to some
| extent python and ruby.
|
| > with exceptions cluttered everywhere
|
| I prefer errors to be part of the return value to exceptions,
| but I also find repeating if err != nil {
| return nil, err }
|
| for almost every line of significant code in go functions
| pretty distracting. I much prefer rust's `?` operator. Or
| haskell's do notation (or scala's roughly equivalent
| for/yield).
|
| > deep inheritance trees that are absolutely useless.
|
| uh, you can have deep inheritance trees in Go, and not have
| them in Java or C++, I'm not sure what your point is.
| fileeditview wrote:
| > Perhaps part of it is the languages we are comparing to?
| I'm comparing to languages such as rust and scala, and to
| some extent python and ruby.
|
| I think you should compare to languages that have a similar
| purpose / area of usage. In my experience that is C++ and
| mostly Java. I wouldn't dare compare dynamically typed and
| interpreted languages with Go.. what's the point? I don't
| have much experience with Rust so I cannot compare it and
| additionally it's rarely used in companies. Scala I just
| don't like personally. For my taste it just "is too much of
| everything".
|
| > ... err != nil ...
|
| In the beginning I was thinking the same. Over time I got
| used to it. When I write it I use a snippet. And clearly
| reading the flow of the error has been beneficial to me a
| lot of times. Yes it is verbose.
|
| > uh, you can have deep inheritance trees in Go, and not
| have them in Java or C++, I'm not sure what your point is.
|
| I am sure you know that there is no inheritance in Go so I
| am not totally sure what you are getting at. My point is
| that I think OOP by composition is a lot clearer than by
| inheritance. Also composition is not overused in Go as say
| inheritance is overused in Java.
| shakow wrote:
| > I am sure you know that there is no inheritance in Go
|
| https://golangdocs.com/inheritance-in-golang
| majewsky wrote:
| This tries really hard to look like an official resource,
| but it seems to be run by some random IT consultancy.
|
| What they call inheritance is a narrow syntactic sugar
| intended to forward method calls to a member without
| having to write something like func (x
| Outer) Frobnicate(val int) {
| x.inner.Frobnicate(val) }
|
| by hand. It's arguably not inheritance because Liskov
| substitution is not permitted: type Outer
| struct { Inner } func process(i
| Inner) { ... } var x Outer process(x)
| // ERROR: type mismatch process(x.Inner) // OK
| thayne wrote:
| > I think you should compare to languages that have a
| similar purpose / area of usage. In my experience that is
| C++ and mostly Java.
|
| The languages I listed first were rust and scala, which
| serve very similar purposes to c++ and java. In fact,
| rust is closer to c++ (no GC, more direct memory control)
| and scala is closer to java (runs on JVM) than go is to
| either.
|
| > Over time I got used to it. When I write it I use a
| snippet.
|
| Which is my point. You have to get used to things like
| this, which probably adds about as much cognitive load as
| having something in the language to reduce this noise
| (although I think this is an known problem in go and may
| be improved in a future version).
|
| > I am sure you know that there is no inheritance in Go
|
| Fine. Replace "inheritance" with struct embedding and/or
| interface hierarchies, and you can get a similar effect.
| My point is you can have overly abstracted designs in
| either language.
| fileeditview wrote:
| > The languages I listed first were rust and scala, which
| serve very similar purposes to c++ and java. In fact,
| rust is closer to c++ (no GC, more direct memory control)
| and scala is closer to java (runs on JVM) than go is to
| either.
|
| The overlap in purpose and usage for Go and Java is
| gigantic. Also I don't understand why it matters whether
| Scala is closer to Java or whether Rust is closer to C++.
| We were comparing Go and X right?
|
| This whole argument tree is a bit nonsensical.. I
| compared Go projects to Java and C++ projects which I
| have worked on. All of the mentioned languages are very
| common in companies these days and are used for similar
| topics. Why bring other languages in to this?
|
| > Which is my point. You have to get used to things like
| this, which probably adds about as much cognitive load as
| having something in the language to reduce this noise
| (although I think this is an known problem in go and may
| be improved in a future version).
|
| You always have to get used to some quirks in any
| language out there. It adds a few lines writing the code
| but reading it is way easier IMHO and not cognitive load.
| Opinions may of course vary on this.
|
| > Fine. Replace "inheritance" with struct embedding
| and/or interface hierarchies, and you can get a similar
| effect. My point is you can have overly abstracted
| designs in either language.
|
| As I already wrote, embedding is rarely used and was not
| a problem ever in my experience and yes interface
| spamming is a problem.
|
| However Java e.g. has these abstraction complexities
| already baked into the standard library and encourages
| the overuse of abstractions IMO.
| philosopher1234 wrote:
| Could you share an example?
| gher-shyu3i wrote:
| Not the poster you're responding to, but practically every
| time someone wants to write code that mimics `map` or
| `filter` and gang is a 5-7 line function (at least).
| Something that would have been a 1 liner in languages like
| Java or C#. It gets tiring and distracting very quickly to
| jump around verbose implementations which amount to nothing
| more than standard streaming functions.
|
| Another example is the lack of annotations for validations.
| In Java or C#, you'd annotate the function argument with
| something like `@Validated`, and it's taken care of. In
| golang, calling the validation code would have to be done
| manually each time that's another 3-4 lines (including error
| handling).
|
| Yet another example is that golang lacks a construct similar
| to C#'s `Task`. You can't execute a function that returns a
| value asynchronously (if you care about that value) without
| creating a channel and passing it.
|
| golang also lacks pattern matching and discriminated unions
| (aka algebraic data types). Java and C# are getting both
| (both have pattern matching, and ADTs are in the works as far
| as I'm aware).
| dullcrisp wrote:
| You got me excited about Java having pattern matching, but
| all I could find was a proposal.
| [deleted]
| gher-shyu3i wrote:
| It's partially there since Java 14: https://docs.oracle.c
| om/en/java/javase/14/language/pattern-m...
| fileeditview wrote:
| Have to agree with you. I wished for sum types & pattern
| matching more than for generics.. but who knows what the
| future brings.
| hderms wrote:
| this is why I have moved to Rust. I worked in Go a number
| of years ago, but after becoming proficient in Scala,
| I've decided sum types and pattern matching are where the
| sweet spot is. The JVM has its own issues though and I'd
| like to have a performance-conscious language in my
| toolbelt that doesn't sacrifice expressivity. Hence:
| Rust.
| cy_hauser wrote:
| RemindMe! 10 Years "Are we there yet?"
| pansa2 wrote:
| > _Go is not an easy programming language. It is simple in many
| ways_
|
| This, exactly. It's worth contrasting this with Python, which has
| a reputation for being simple but is actually extremely complex -
| but it _is_ easy (at least for small programs).
|
| However, I can't help thinking that the two shouldn't be in
| opposition. IMO it should be possible for a language to be both
| simple and easy to use.
| vinger wrote:
| Meet php.
| pid_0 wrote:
| Yeah I agree with this. Go is awesome but pretty complicated.
| Channels still break my brain every time I use them.
| TurboHaskal wrote:
| The blog post misses the point. Of course writing idiomatic,
| gotcha-free, production ready code is never easy.
|
| But Go itself is comparatively easy. By far the fastest I have
| ever onboarded new engineers with, even more so than Python
| (which is terrible but somehow has a good reputation at being
| easy and friendly).
| aalhour wrote:
| What is horrible about Python wrt onboarding new engineers?
|
| In my personal experience, I had an easier time getting
| onboarded on Python than Golang.
| TurboHaskal wrote:
| Everything that happens in between development and
| deployment.
| [deleted]
| taeric wrote:
| Conversely, I think go can be pushed as an easy language. In
| large, whether a language is "easy" seems dominated by
| perception.
|
| That is, most things that are "hard" have a reputation as being
| hard by non practitioners.
|
| Which isn't to say that empirically it is clear some languages
| make some errors easier to make.
| iandanforth wrote:
| Barriers to entry are invisible. They are invisible to people on
| the inside and most frequently invisible to people who have a
| hand in creating those barriers.
|
| I once had a conversation with a founder about their signup flow.
| This founder had aspirations to be a global player. They said
| that step 2 of their flow included entering a credit card number.
| I had to stop them. "What about people who don't use credit
| cards?" They were nonplussed. "You know, China, much of Africa
| ..." they had never actually thought about whether people had
| credit cards because every single person they interacted with had
| one.
|
| Back to languages. If you've never taught an introduction to
| computer programming for a general audience you are blind to what
| does and does not matter about a language. You look at things
| like `foo.bar()` and think "Yeah that's a simple method
| invocation" and have no idea how many people you just lost.
|
| Never underestimate how much ease of use matters. We, as a
| community, have selected for decades _against_ people who care
| about language ergonomics because they get hit in the face with a
| wall of punctuation filled text and turn away. We fight over
| where braces belong while not realizing how many hundreds of
| thousands of people we 've excluded.
|
| Ease of use is _the_ most important part of a language. It 's
| just that the barriers to entry are invisible and so the people
| who happen to be well suited to vim vs emacs debates get to talk
| while the vast majority of potential programmers are left on the
| outside.
|
| We create barriers to entry accidentally because we design for
| people like us. To us the barriers are invisible.
| jodrellblank wrote:
| I don't think you can write that much of a rant using the term
| "ease of use" without explaining your view on what "ease of
| use" is; does "use" really only mean a beginner using it for
| the first time? You mention languages with punctuation, what
| alternatives do you see instead? You claim "ease of use is THE
| most important part of a language", but why is it the most
| important?
| allenu wrote:
| > Ease of use is the most important part of a language.
|
| It's really a matter of who your target audience is and what
| they're trying to achieve, though, isn't it? You might make a
| language easy for the whole world to use, but simultaneously
| make it hard for specific tasks. Likewise, a language might be
| easy to use for experts who are trying to achieve a specific
| task, but difficult for newbies. That's totally okay.
|
| BASIC was super easy to understand and helped me get into
| programming, but there's no way I would use it for anything
| serious today.
| onlydeadheroes wrote:
| I agree completely and I would add that the less mental effort
| you are spending on your language, the more you are using on
| your problem. This can have big effects on code and design
| quality, and allow you to grasp harder problems.
| solosoyokaze wrote:
| A programming language is a tool. I don't think it should
| really be optimized or designed around how easy it is to teach
| someone who is at step zero, basic programming concepts. I _do_
| think Go is a decent language to teach up and coming
| professional devs precisely because it doesn't hide the real
| complexity of what's going on, but I'd probably opt for
| something a bit higher level as the absolute intro to
| programming.
| itake wrote:
| I've been coding for 10+ years in primarily ruby and python.
| I switched to golang recently for work. While its an
| interesting language, it takes forever to express what I want
| the code to do, unlike the previous languages.
|
| Go's simplicity forces developers to introduce insane amounts
| of complexity.
| solosoyokaze wrote:
| Ruby and Python are notorious for being difficult to read
| because of all the foot guns in place. Monkey patching,
| duck typing, metaprogramming... and that's not even talking
| about structural limitations like the GIL.
|
| Go is definitely more verbose and less "fun" to write, but
| it's 10X easier to reason what's going on in a large
| application.
|
| Of course, it's also the correct kind of solution for some
| types of problems. If you need a compiled language (many
| do), the competition to Go isn't Ruby and Python, it's C++
| and Rust.
| cb321 wrote:
| Or Nim or Zig or ...
|
| Actually, I feel like Zig may be most in the compiled-
| but-keep-it-simple-like-C headspace as Go. I don't know
| either Go or Zig super well. From what I do know, Zig
| seems not quite as stubborn as Go about forcing code to
| be "simple to reason about" (which is _also_ subjective,
| though maybe less so than "easy to do things").
| dnautics wrote:
| I would say Zig is interesting in that "safe things are
| easy and pretty", "unsafe things are difficult and ugly",
| thus drawing eyes to the code that needs it. It's four
| lines of nasty looking code for me to convert a slice of
| bytes to a slice of (f64/f32/i32)... Which the compiler
| no-ops. This is dangerous because the alignment and byte
| count must be correct.
| solosoyokaze wrote:
| I would say both of those are interesting languages, but
| lack the amazing standard lib of Go and definitely the
| tooling and industry support.
|
| Personally, I find Swift to be a really great language,
| but can't deal with the Apple eco-system, XCode, raw
| Linux support...
| cb321 wrote:
| Totally agree Re: Swift drawbacks.
|
| Nim's stdlib is actually quite large (like 300 modules).
| I am sure there are things in Go's that are not in Nim's
| (like Go's `big`), but I am equally sure there are things
| in Nim's that are not in Go's (like Nim's `critbits`,
| `pegs`, etc).
|
| I doubt there is much in Go's stdlib not available in
| Nim's nimble package system, but OTOH, I am also sure the
| Go ecosystem is far more vast. I just didn't want people
| left with the impression Nim's stdlib is as small as
| Zig's which it is definitely not. Nim has web
| server/client things and ftp/smtp clients, json and SQL
| parsers, etc.
| jacobr1 wrote:
| > If you need a compiled language
|
| I would wager jvm/clr are probably the main competition
| for Go at least for product/tech companies and enterprise
| IT.
| solosoyokaze wrote:
| Definitely for server-side software, but not for binaries
| that need to be distributed to end users.
| eeZah7Ux wrote:
| Python is widely considered to be very expressive and
| very readable.
|
| You can write unreadable code in virtually any language
| but that's besides the point.
| solosoyokaze wrote:
| Considered "very readable" by whom? It's pretty well
| accepted that dynamic/weakly typed languages are more
| difficult to deal with at scale.
| prionassembly wrote:
| Python is dynamically but strongly typed. "2+True" is a
| type error; once something has a type, it has a type.
| There's no WAT.
| solosoyokaze wrote:
| Variables can change type though. This is working python
| code: x = 1 y = 2 print(x +
| y) x = "no " y = "types" print(x +
| y)
|
| Whereas this will not even compile in Go:
| x := 1 y := 2 x = "no" y =
| "types"
| anaerobicover wrote:
| That's the difference between static and dynamic typing,
| not the difference between weak and strong. The _values_
| cannot be used as if they were another type.
| anaerobicover wrote:
| > what's the type signature of `print()` then?
|
| Dynamic typing doesn't rule out polymorphism.
| void print(PyObject objects...)
|
| where `PyObject` is a base type.
| solosoyokaze wrote:
| So what's the type signature of `print()` then?
|
| I do think my example code demonstrates why Go is easier
| to reason at scale than Python. Python is conflating
| assignment and type declaration. Go has = and :=, so it's
| crystal clear what's going on.
| anaerobicover wrote:
| > what's the type signature of `print()` then?
|
| Dynamic typing doesn't rule out polymorphism.
| void print(PyObject objects...)
|
| where `PyObject` is a base type.
|
| Additionally, you could perfectly well have constants, or
| require variables to have a fixed type, in a dynamic
| language. You would just pay a cost at runtime to check
| the type on assignment.
| solosoyokaze wrote:
| I would argue that you pay the cost at runtime but you
| also pay a cognitive overhead cost while writing in a
| dynamically typed language. Refactoring in particular is
| a lot more difficult.
| joshspankit wrote:
| > Back to languages. If you've never taught an introduction to
| computer programming for a general audience you are blind to
| what does and does not matter about a language. You look at
| things like `foo.bar()` and think "Yeah that's a simple method
| invocation" and have no idea how many people you just lost.
|
| I keenly remember in college when they were first starting to
| teach me C++ and I asked something like "Ok: I hear what you're
| saying that this is a function, and that's a parameter, but my
| question is _how does the computer know that you've named it
| [whatever the variable name was]_?
|
| The teacher had no understanding that this was a conceptual
| barrier.
|
| Of course now I know that the answer is "because order of the
| syntax tells it so", but stuff like that made those classes
| much harder than they needed to be.
| cannabis_sam wrote:
| > Barriers to entry are invisible. They are invisible to people
| on the inside and most frequently invisible to people who have
| a hand in creating those barriers.
|
| I just want to say that I really, really like this phrasing.
|
| I have a lot of opinions on how programming languages could be
| improved, and however much I disagree with Rob Pike on types, I
| still think Go hit a real sweet spot.
| numlock86 wrote:
| > "What about people who don't use credit cards?"
|
| > "You know, China, much of Africa ..."
|
| Oh, you don't even have to go that far. Of all the people I
| know maybe 1 out of 10 has a credit card. The irony in regard
| to your post: Most of them got one because "it was needed for
| something online" at some point. This is in Germany.
| sangnoir wrote:
| Programming has and almost infinite _depth_ to it, depending on
| the complexity you are tackling /modeling. In a way, it is like
| mathematics in the way you have layers acting as
| foundations/abstractions for more complex representation.
| Introduction to mathematics is counting discrete things, then
| the number line, addition, which is a foundation for/abstracted
| by multiplication, which is in turn a foundation for/abstracted
| by exponents, etc. and in no time, you're at the level of
| multi-variate calculus and n-dimensional hypercubes.
|
| Each level requires notation that is concise (so it can fit in
| short-term memory to be actually useful) - one cannot expect
| graduate-level mathematics to share the same notation/symbols
| as beginner (elementary) maths.
|
| Programming languages have to trade off which level they are
| biased towards, I do not believe in a universal language that
| is both easy for beginners while being concise for advanced
| users.
|
| Back on topic - Go is not an _easy_ language, it is a _simple_
| language, those 2 things are not the same.
| Zababa wrote:
| > Back on topic - Go is not an easy language, it is a simple
| language, those 2 things are not the same.
|
| I often see this point here, but I always wonder what people
| mean by it. Could you elaborate on that point?
| epicide wrote:
| Not OP, but I'd be happy to try and differentiate, as well.
|
| Simple means uncomplicated, not a lot of pieces. A violin
| is arguably simpler than a guitar because of a lack of
| frets.
|
| Easy means it is not difficult. A guitar is arguably easier
| than violin [0] because it has frets.
|
| It's important to remember that "easy" is very subjective.
| What is easy for one person might be insurmountable to
| another.
|
| tl;dr "simple" usually means "easy to understand" and
| "easy" usually means "easy to do". Both inherently assume
| some amount of prior knowledge or skill, so neither is
| entirely universal or objective.
|
| [0]: I'm not trying to say one instrument is _better_ or
| disparage and guitarists. It 's an example.
| steveklabnik wrote:
| The primary source for this is
| https://www.infoq.com/presentations/Simple-Made-Easy/
|
| (I am not entirely sure I agree with its thesis or its
| applicability to Go, but since nobody had actually linked
| you directly to the concept, I thought it would be
| worthwhile to do so.)
| tyingq wrote:
| I think the author's example is pretty good.
|
| _" How do you remove an item from an array in Ruby?
| list.delete_at(i)...Pretty easy, yeah?"_
|
| _" In Go it's ... less easy; to remove the index i you
| need to do:"_ list = append(list[:i],
| list[i+1:]...)
|
| So Go is simple in that it doesn't have shortcut functions
| for things. There's generally one way to do things, which
| is simple. But it's not easy, because it's certainly not
| intuitive that "append" is the way to remove an array
| element.
| sangnoir wrote:
| To me, what makes Go simple language is limited set of
| building blocks, and being very opinionated - this mostly
| relates to syntax and what the core language provides out
| of the box. With Go, you only need to understand looping,
| branching, channels, slices and you're mostly good to go.
|
| Ease is measured by how a language may be used to solve a
| problem. As an example, text-wrangling with Perl is _easy_
| - but you may have to do it using complex (i.e. not simple)
| representation.
|
| Back to Go - channels are a _simple_ concept, but are not
| (always) _easy_ to work with if you do not put a lot of
| thought into your concurrency model.
|
| Edit: I just thought of another way to express the
| difference between "simple" and "easy". The notation of
| adding "1+1=2" is _simple_ , however _proving_ that
| "1+1=2" is _not easy_ (at least at the level of elementary
| students).
| kstenerud wrote:
| Go isn't even simple. They tried to be simple, but mostly
| managed to only move the complexity around, or foist it on to
| the user's shoulders by not providing a layered, composeable
| API in the standard library.
|
| There are some kinds of complexity you can't eliminate; only
| contain through careful, thoughtful architecture.
| Unfortunately, people often confuse this with the
| eliminatable kind, and end up making a bigger and bigger mess
| as they push things around.
| t0astbread wrote:
| What do you mean by "layered, composeable API"? Go does
| provide abstractions for common use cases in its standard
| library (`io.ReadAll` for example) and there are interfaces
| to connect different parts of the standard library
| (`io.Reader`/`Writer`, `fs.FS` etc.).
| epicide wrote:
| > Go is not an easy language, it is a simple language, those
| 2 things are not the same.
|
| This distinction is fundamental to any sort of design, but is
| unfortunately lost on a _lot_ of people (especially
| developers, in my experience). Easy and simple are near-
| universally conflated.
|
| > I do not believe in a universal language that is both easy
| for beginners while being concise for advanced users.
|
| Exactly. I really want some languages to be more _intuitive_
| and easy to pick up for non-programmers. The average person
| (whatever that means) does not have the first clue about the
| machines that run so many parts of their lives.
|
| At the same time, such a language would likely be a bad fit
| for most professional software development. That hardly means
| it's without value.
|
| I know languages like that exist, but they're often aimed at
| absolute beginners and are treated like toys. There doesn't
| seem to be much middle ground or "transition languages".
|
| Insert joke about how $LANGUAGE_YOU_DISLIKE is a toy.
| jacobr1 wrote:
| It seems like Basic has been the closest we've come on a
| general purpose language with "easy to pick up features"
| (at least in widespread use)?
| msiemens wrote:
| > Easy and simple are near-universally conflated.
|
| That's an interesting statement as it doesn't always work
| in other languages. In German (my native language) my first
| instinct was to translate both words as "einfach" which
| contains both concepts. In fact, in my online dictionary of
| choice the word "einfach" is the first entry for both
| "easy" and "simple". So if Germans conflate these two it
| might be because of the language they speak :) But more to
| the point, I'm wondering how universal the distinction
| between easy and simple is when other languages cannot
| express that distinction as easily as in English.
| malloci wrote:
| > The average person (whatever that means) does not have
| the first clue about the machines that run so many parts of
| their lives.
|
| Sadly, I would argue that this is also true of many
| developers.
| jacobajit wrote:
| I mean, Python is the de facto beginner language and also
| used for professional software development (and of course
| intermediate data science work). Are you suggesting this is
| an unwise or unstable equilibrium?
| epicide wrote:
| I'm glad you bring that up. I think Python is the closest
| we have to a "universal language" (even so, it still has
| some limitations).
|
| I think it works well for beginners because the language
| itself is so consistent and they have put a lot of effort
| into avoiding corner cases and "gotchas". And I think it
| works for professional uses because of third party
| library support.
|
| To answer your question: I'm not suggesting that at all.
| I'm honestly not entirely sure how Python balances it
| seemingly so well. Given the lack of focus in the
| industry towards "intermediate" programmers and use
| cases, my slight fear is that Python will be shoehorned
| into one direction or the other.
|
| Even if the language itself isn't, it does feel like the
| use-case-complexity gap is growing exponentially, at
| times.
|
| And not just with Python. Seemingly, you're either a
| complete beginner learning conditionals and for-loops or
| you're scaling out a multi-region platform serving
| millions of users with many 9's of uptime.
| est wrote:
| > You know, China
|
| Another thing I find very interesting is Go is very popular in
| China
|
| https://blog.jetbrains.com/go/2021/02/03/the-state-of-go/
|
| the US ranked #7, it barely had more devs than say, Belarus.
| edoceo wrote:
| But the metric was credit cards in China
| dm03514 wrote:
| I love go, but I think it is a straight up dangerous language. I
| believe this because:
|
| - The tiny standard library and low learning curve make it quite
| easy to get started.
|
| - Concurrency is a first class citizen, through goroutines and
| channels
|
| - The performance, combined with the low barrier to entry,
| combined with its popularity make it very desirable for companies
|
| In my experiences this means a lot of companies without deep
| concurrent programming experience begin to adopt it. In my
| experiences this means concurrent programming errors....literally
| EVERYWHERE. I've seen (and created these of course) in every
| company I've been at and in huge open source go projects.
|
| I wish go would address this through first class tooling or type
| supported in the language like rust.
|
| I have a hueristic based approach that I've had success with when
| writing and reviewing code, but writing a static analysis tool
| for it is a bit out of my experience:
|
| https://medium.com/dm03514-tech-blog/golang-candidates-and-c...
| candiddevmike wrote:
| Can you expand on the types of errors you're seeing? Go has a
| fairly powerful race detector you can run as part of your test
| suite that seems to catch everything I've thrown at it.
| dm03514 wrote:
| Me too! I generally think that the race detector is the best
| approach right now.
|
| At a prior job we had a lot of code that wasn't structured in
| a way that made it easy to exercise using the race detector.
| This was combined with misunderstanding about what the race
| detector did and didn't do. For example there was one team
| that ran `-race` on non-concurrent code, and expected it to
| verify race conditions.
| captainbland wrote:
| I'd say that Go in its current form is easy but tedious.
| Concurrency has some ways to shoot yourself in the foot but
| that's pretty much par for the course. The good news is it's
| going to become a lot less tedious if a little bit less easy with
| the introduction of generics.
| fishywang wrote:
| There's so much wrong with the second example (it works, yes, but
| for all the wrong reasons) I don't even know where to start.
|
| Using the buffer size of a channel to control number of
| concurrent jobs is just the wrong approach. It's so much easier
| and cleaner to just use the number of goroutines for that:
| const workers = 3 const jobs = 20
| jobsChan := make(chan int) var wg sync.WaitGroup
| for i := 0; i < workers; i++ { wg.Add(1) go
| func() { defer wg.Done() for work :=
| range jobsChan { time.Sleep(time.Second)
| fmt.Println(work) } }() }
| for i := 1; i <= jobs; i++ { jobsChan <- i }
| close(jobsChan) wg.Wait() fmt.Println("done")
|
| One thing about channels in go is that the only time you want to
| use a buffered channel is the time that you know exactly how many
| writes (n) you'll have to the channel, while n is finite and
| reasonably small, so you create a channel with buffer size n to
| unblock all the writes. An example to that is that if you want to
| strictly enforce a timeout to a blocking call:
| const timeout = time.Millisecond*10 ctx, cancel :=
| context.WithTimeout(context.Background(), timeout) defer
| cancel() resultChan := make(chan resultType, 1)
| go func() { result, err := myBlockCall(ctx)
| resultChan <- resultType{result, err} }() select
| { case <- ctx.Done(): return nil, ctx.Err()
| case r := <- resultChan: return r.result, r.err
| }
|
| If you are using a buffered channel in other cases, you are
| likely doing it wrong.
| tptacek wrote:
| I don't think that the claim has ever been that Go is especially
| easy to program in; it's about as good as any other mainstream
| language for expressing logic. It's that Go is simple enough that
| the language gets out of your way really quickly.
|
| I think the article kind of got the idea midway through, at "I
| think most programmers can figure out what the above does even
| without prior Go experience". Yes. That's the idea.
| hombre_fatal wrote:
| I think anyone can agree with the author that Go definitely lacks
| conveniences you're used to in other modern languages.
|
| I just think we also tend to dramatize how much that matters.
|
| I also think Go's benefit really is that it's simple and that you
| have very few tools that let you do anything other than focusing
| on solving your problem, and like the author I do think
| goroutines are the exception to that.
|
| Where I work, we don't even use them. We use plain ol' mutexes
| instead.
|
| When coming from higher level languages, Go does feel
| frustrating. In Javascript, you're writing `const [a, b, c] =
| await Promise.all([x(), y(), z()])`. In Go, you're writing 40
| lines of WaitGroup code. It's easy to go ughhh.
|
| But I think a nice way to appreciate Go's conservative
| middleground is to go back to writing some C/C++ code for a
| while, like some Arduino projects. Coming from that direction, Go
| feels like a nice incremental improvement that doesn't try to do
| too much (with perhaps the exception of goroutines).
|
| Go's performance is also particularly stand-out which makes up
| for many of its convenience shortcomings. It's fast enough that
| I've written some code in Go where I would have written C not
| long ago. And writing C involves quite a bit more concessions
| than what Go gives you, so in comparison, Go kinda spoils you.
|
| Go has plenty of annoyances too though. Not having any dev vs
| prod build distinction is annoying. Giving maps a runtime penalty
| of random iteration in order just to punish devs who don't read
| docs is annoying. It's annoying to have crappy implementations of
| things like html templating in the stdlib which steal thunder
| from better 3rd party libs. Not being able to shadow vars is
| annoying. `val, err :=` vs `val, err =` is annoying when it
| swivels on whether `err` has been assigned yet or not, a footgun
| related to the inability to shadow vars. etc. etc.
|
| But it's too easy to overdramatize things.
| arp242 wrote:
| I don't think this article really overdramatizes things; if I
| had written "zomg Go is terrible look what I need to write to
| delete an item from a list!" - a sentiment I've seen expressed
| a few times over the years - then, yeah, sure.
|
| But that's not really what the article says. As it mentions, I
| don't think these are insurmountable issues, and I still like
| and use Go: it's pretty much my go-to language these days.
| Overall, I think it did a lot of things right. But that doesn't
| mean it's perfect or can't be improved.
| hombre_fatal wrote:
| I used "dramatize" to describe the blog post (in such that
| the blog post was written) and then "overdramatize" at the
| end in reference to my own infinite list of annoyances with
| Go and the normal course of discussion on HN about proglangs.
|
| The blog post does admit "Are these insurmountable problems?
| No." Learning it in 5 minutes certainly is a stretch as you
| point out, but I'd still say you can learn it in a weekend to
| a greater degree than any other language I use.
|
| Though you'll notice my comment after the first sentences is
| a way to share my own thoughts on Go, an opportunity I'm
| never going to neglect.
| ganafagol wrote:
| The curse of having landed on the HN front page. You write a
| little blog post with some random thoughts, somebody found it
| insightful and posts it to HN and if more people like it, it
| ends up on the front page. That exposure tends to attach a
| wide-ranging seriousness to this kind of blog post which may
| dramatically overinflate the point that it originally
| intended to make.
| simiones wrote:
| > I also think Go's benefit really is that it's simple and that
| you have very few tools that let you do anything other than
| focusing on solving your problem
|
| I feel just the opposite: Go has very few tools, which forces
| you to often have to solve language games instead of focusing
| on your problem.
|
| You want to remove an element from a list? Instead of writing
| that, you need to iterate through the list and check if the
| element at position i has the properties you want, and if it
| does, copy the list from i+1 (if it wasn't the last!) over the
| original list. This is NOT the problem you were trying to
| solve.
|
| You want to store a map from some struct to another? Instead of
| writing that, you have to create a key struct that is
| comparable for equality from your original struct, which
| probably involves string concatenation and great care to avoid
| accidentally making unequal structs have equal keys; and then
| two maps, one from keys to the key struct and another from keys
| to the value struct, and of course client code also has to know
| about this explicitly.
|
| You want to pass around some large structs, or iterate over
| slices of them? Well, you'd better start using pointers, and
| start being careful about what is a copy and what isn't,
| otherwise you'll pay a steep performance price, without any
| syntactic indication.
|
| You want to work with sets of values? You'll have to store them
| as map keys, perhaps doing all of the black voodoo described
| above. And of course, you can't do a reunion of two sets, you
| have to iterate through the keys of the first map and the
| second map and add them to a third map.
|
| All of these things are annoying when you are writing them, and
| even more of a problem when you are reading the code, as you
| have to understand the intent from the messy internals that Go
| forces you to expose.
| nemo1618 wrote:
| > Giving maps a runtime penalty of random iteration in order
| just to punish devs who don't read docs is annoying
|
| Can you explain what you mean by "runtime penalty?" Last I
| checked, the iteration is only random wrt its starting offset;
| after that, it proceeds linearly through the buckets. So you
| only generate one random integer per `range`, which seems
| acceptably cheap.
| scaladev wrote:
| It still seems to be working as you described.
|
| https://github.com/golang/go/blob/db8142fb8631df3ee56983cbc1.
| ..
| grey-area wrote:
| Great summary of areas where Go could be improved for real
| world use. I'd want any of these improvements over delete at
| index in a slice (which I've never wanted).
| lmm wrote:
| But why would you be comparing to C? I've yet to find any
| scenario where Go is an option and e.g. OCaml isn't (unless the
| scenario is "I want to use a language whose creator called it a
| "systems language"").
| throwaway189262 wrote:
| I frequently describe Go as "C, with memory safety and GC". I
| don't run into many that disagree.
|
| Go is basically how Java was pre-generics. People forget that
| early Java was much like Go in some respects. Everything casted
| to object. Not really sure what the object is unless you wrote
| the code. Lack of common patterns and a rich set of libraries
| (Guava, Apache Commons).
|
| Early Java was supposed to be safe C++. Converting C++ to Java
| is still trivial unless there's pointer craziness. Generally
| copy paste the C++ and change naming conventions.
|
| I hate working on unfamiliar Go projects. Copy pasted code
| everywhere. And everyone builds their own abstractions because
| there aren't enough.
|
| I don't understand Go's appeal. To me it feels clunky and stuck
| in the 90's. Designed by C programmers for C programmers.
|
| Like C it has no generics, bad packaging, and is a pain in the
| ass to cross compile. Its only killer feature, IMO, is it's
| fiber threading model and that's quickly being copied by other
| languages, even ancient Java.
| sofixa wrote:
| > Like C it has no generics, bad packaging, and is a pain in
| the ass to cross compile.
|
| Go modules are pretty good packaging IMHO. What don't you
| like about them?
|
| As for cross-compiling, are you joking? It's very easy and i
| literally cannot imagine it being easier. What's painful
| about it?
| simiones wrote:
| I would say Go has very poor packaging, but this is when
| comparing it with Java (maven), Rust etc. It's definitely
| much better than C.
|
| The biggest reason why Go modules are a very poor packaging
| solution is the horrendous stupidity of tying them to
| source control. This makes it difficult to develop multiple
| modules in the same repo, it requires your source control
| system to know Go, it makes internal refactors a problem
| for all consumers of your code (e.g. if you are moving from
| github to gitlab, all code consuming your package will have
| to change their imports).
|
| The versioning ideas of their module system, particularly
| around the v2+, have made it such a pain that there still
| isn't a single popular package that uses the proposed
| solution so far, not even Google ones like the Go protobuf
| bindings.
| sofixa wrote:
| > The biggest reason why Go modules are a very poor
| packaging solution is the horrendous stupidity of tying
| them to source control
|
| I think it's actually a great idea. It uses native things
| you use anyway, avoids an unnecessary third party ( a
| package repository like pypi), and adds in extra security
| ( remember those hijacked pypi credentials and pypi-only
| malicious packages that weren't in source control, so
| hard to verify?).
|
| > This makes it difficult to develop multiple modules in
| the same repo
|
| How so? You can justs use different folders and packages.
|
| > requires your source control system to know Go
|
| Not really.
|
| > it makes internal refactors a problem for all consumers
| of your code
|
| That's always the case - regardless of your package
| repository, if you change it, you have to update the new
| references everywhere. You can use go.mod to replace a
| reference ( e.g. use gitlab.com/me/library instead of
| github.com/legacy/library) without updating all the code.
| And then there's the goproxy protocol, which makes all of
| this optional.
|
| I agree on v2 modules, that is very weird.
| zimpenfish wrote:
| > It's very easy and i literally cannot imagine it being
| easier.
|
| `go build -target x.y` could be considered fractionally
| easier than `GOOS=x GOARCH=y go build`? But yeah, this is a
| nonsense claim by GP.
| solatic wrote:
| > I hate working on unfamiliar Go projects. Copy pasted code
| everywhere. And everyone builds their own abstractions
| because there aren't enough.
|
| Arguably, if the project requires lots of abstractions and
| copy-pasted code, then the project shouldn't be written in
| Go. Just because you can, technically speaking, write object-
| oriented code in Go does not mean that it is ergonomic to do
| so or a recommended use-case for the language. Projects that
| benefit from object-oriented design should stick to languages
| with first-class support for it.
| cy_hauser wrote:
| I don't disagree with your conclusion but I don't see it
| following from the quoted premise. What was your thought
| chain to get from the quoted text to OO stuff?
| solatic wrote:
| There's a difference between using a struct to represent
| concepts like program configuration or grouped function
| parameters, and shoehorning OO-native design concepts
| like the decorator pattern that need inheritance into a
| language that doesn't support dynamic dispatch. Much of
| the bad Go code I see comes, yes, from overusing
| abstractions and lots of copy-pasting, but most of _that_
| comes from trying to fit square pegs like OO design into
| Go 's round hole.
| cy_hauser wrote:
| Still don't get the connection in the way you do. Maybe
| I'm so used to OO stuff that I just gloss over it.
|
| > a language that doesn't support dynamic dispatch.
|
| Go's interfaces do support this enough that I consider it
| worth at least mentioning.
|
| https://en.wikipedia.org/wiki/Dynamic_dispatch#Go_and_Rus
| t_i...
| Tobu wrote:
| In fact, Go is not even memory safe despite the GC. It has
| data races, which are exploitable. -
| https://blog.stalkr.net/2015/04/golang-data-races-to-break-
| memory-safety.html -
| https://golang.org/doc/articles/race_detector#Introduction
| otabdeveloper4 wrote:
| > Converting C++ to Java is still trivial unless there's
| pointer craziness. Generally copy paste the C++ and change
| naming conventions.
|
| What? You've obviously never seen actual C++ code to make
| such a ridiculous statement.
| pjmlp wrote:
| 1996 C++ (when Java appeared) being used in MFC, OWL,
| Powerplant and CSet++, alongside Smalltalk in the gang of
| four book, definitely.
| nvarsj wrote:
| Google C++ looks a lot like Java, maybe that's where they
| are coming from. But it's still quite a stretch!
| otabdeveloper4 wrote:
| As they say, "you can write FORTRAN in any language".
| strken wrote:
| Go's lack of generics and metaprogramming can be extremely
| frustrating when you're working with services that copy data
| around from one struct to another, which ends up being a lot of
| them. You're left with reflection, code generation, or doing it
| field by field.
|
| It'd be nice to have syntactic sugar for splatting structs
| together, or a library that let you do it in a typesafe and
| efficient way.
|
| EDIT: I will defend the random map order, though. Golang maps
| were only predictable up until a set number of items, which
| caused hard-to-detect bugs where your small test case would be
| in order but your large real-life data would be out of order.
| hombre_fatal wrote:
| I certainly agree. I would love for Go to get to a place
| where I never have to copy and paste code again. Generics
| will get us part way there.
|
| The next item on my wishlist are algebraic data types. In
| complex projects I've opted for Rust over Go just because of
| the abstraction power of being able to represent things like:
| Dest = Register (A | B | C) Src = Register (A | B |
| C) | Immediate value OpCode = Nop | Add(Dest, Src) |
| ...
|
| In Go, I'm so tired of `interface{}`.
|
| Edit: To respond to your edit about map iter order, that's
| related to my complaint of Go's lack of dev vs release build.
| Randomizing map iter order in dev builds is acceptable to me.
| Go makes you pay for that in prod.
| yuribro wrote:
| I don't understand the jump from the lack of algebraic
| types, to using interface{}.
|
| You can spell out all your types into explicit structs
| (where you can choose between a simple implementation with
| a little worse performance or to make the implementation a
| little more complex) and pass them around.
|
| There are many complex code bases (kernels & databases come
| to mind) which use C (not C++), and they don't resort to
| passing void* around the "business logic" parts.
|
| The idea that for a complex project you would choose a
| different language with so many different characteristics
| due to a minor detail about the type system (this is not
| exactly Haskell vs JS...). This kind of decision making
| would not pass in any reasonable organization...
| kaoD wrote:
| I don't know Go. How can you emulate sum types on it? And
| how do you pattern match them?
| simiones wrote:
| It depends on what you mean exactly by "emulating sum
| types" and "pattern matching them".
|
| An example is rolling your own discriminated union:
| type Avariant struct { a int } type
| Bvariant struct { b string } type
| SumChoice int const ( SumA SumChoice = 0
| SumB SumChoice = 1 ) type Sum struct {
| type SumChoice A *Avariant B *Bvariant
| } sumA := Sum{choice: SumA, A: &{a: 7} }
| sumB := Sum{choice: SumB, B: &{b: "abc"} }
| func foo (x Sum) { switch(x.choice) {
| case SumA: { fmt.Printf("Value is %d", x.A.a)
| } case SumB: {
| fmt.Printf("Value is \"%s\"", x.B.b) }
| } }
|
| As usual with Go, it's ugly and verbose and error prone,
| but it just about works.
|
| The previous poster was probably thinking of something
| similar but using interface{} (equivalent to Object or
| void*) instead of Sum, and then pattern matching using an
| explicit type switch: func foo (x
| interface{}) { switch actual := x.(type) {
| case AVariant: { fmt.Printf("Value is %d",
| actual.a) } case BVariant: {
| fmt.Printf("Value is \"%s\"", actual.b) }
| } }
|
| This is slightly less risky and has less ceremony, but
| it's also less clear which types foo() supports based on
| its signature. Since Go has only structural subtyping [0]
| for interfaces, you would have to add more boilerplate to
| use a marker interface instead.
|
| [0] In Go, a type implements an interface if it has
| functions that match (name and signature) the interface
| functions. The name of the interface is actually
| irrelevant: any two interfaces with the same functions
| are equal. So if you declare an interface MyInterface
| with no methods, it is perfectly equivalent to the built-
| in `interface{}`: any type implements this interface, and
| any function which takes a MyInterface value can be
| called with any type in the program.
| kaoD wrote:
| Thanks for the write up effort.
|
| On the first approach, with "error prone" you mean the
| tag could be incorrect, right? (or even have an
| impossible variant with 0 or multiple variants set).
| simiones wrote:
| Yup. Also, there is no exhaustiveness checking (the
| constants we chose to define for SumChoice(0) and
| SumChoice(1) mean nothing to the compiler anyway -
| exhaustiveness checking would have you test any possible
| int, since SumChoice is simply an alias for int).
| BenFrantzDale wrote:
| But are those two variants held in memory inside Sum or
| are they heap-allocated sitting out on some other cache
| line? Can one write a performant Sum type?
| simiones wrote:
| With pointers, they are in the heap, but at least there
| is no overhead. You could also declare them embedded
| (without the *s), but then the Sum struct would have a
| size that is a sum of all the variants' sizes, instead of
| a proper union which should have the max size (more or
| less). type Sum struct { type
| SumChoice A Avariant B Bvariant }
|
| This is what I meant by saying that it depends on exactly
| what you mean by "sum types".
| BenFrantzDale wrote:
| Got it. Although I'm not sure what "no overhead" means if
| the instances have to live way far away on the heap. That
| means you've got an alloc (including a mutex lock), then
| the value lives far away, then the garbage-collection
| overhead then the delete. When I think sum-type I think a
| flag plus storage for the maximum size and alignment
| type, and all of the above bookkeeping and cache misses
| go away.
| simiones wrote:
| Yes, you're right - I was thinking of "no space
| overhead", and even that is not entirely correct (you pay
| an extra pointer per variant size, which in this case
| would be some overhead over the embedded version).
|
| Still, I think most people don't worry so much about
| efficient packing of sum types, and instead the safety
| and convenience factors are more important. Of course,
| YMMV based on exact usage.
|
| I'm not in anyway claiming that Go supports sum types.
| Someone just asked how they may be emulated, and I don't
| think it should be surprising that emulation has an
| overhead.
| kuschku wrote:
| > (this is not exactly Haskell vs JS...
|
| No, it's more like Haskell vs C, considering the
| expressiveness of Rust's vs Go's type system.
| xpressvideoz wrote:
| I agree with that. Go can be frustrating if you expect the
| level of expressiveness of Python, Java, or Rust. But if you
| regard it as a better C, you can appreciate its ergonomic
| upgrades compared to C. What is unfortunate is that Go could be
| much more than just a better C, considering Google's potential
| and the decades of programming language research that were
| present at the inception of Go. By trying to simplify things
| too hard, and ignoring precedents, Go missed a huge opportunity
| cost. And it even failed to fulfill its original intent: it
| cannot be used in place of C, unlike Rust.
| hombre_fatal wrote:
| Agreed. I would love for Go to have stolen more pages from
| Rust's crusade for zero-cost abstractions and high level
| conveniences. I can appreciate ruthless conservatism in some
| places, but Go isn't actually ruthlessly conservative. It has
| quite a lot of weird things in its stdlib.
| systemvoltage wrote:
| Could you please expand on why is Go not a replacement for
| Rust? Is that because of the lack of manual memory
| management?
| hombre_fatal wrote:
| The obvious case is embedded programming. You can run Go
| via a custom compiler for microcontrollers:
| https://tinygo.org/.
|
| But to their point: why incur that sort of overhead for a
| language that doesn't necessarily give you enough
| conveniences to be worth it? Or rather, Rust gives you more
| conveniences with very little runtime overhead.
|
| It's not exactly damning to be unable to compete with C on
| a microcontroller with 2K RAM, they were just pointing out
| the end of my comparison with C/C++.
| xpressvideoz wrote:
| What I wanted to say was that Go was not a replacement for
| C, compared to Rust which could have similar performance
| characteristics as C or C++. The lack of manual memory
| management is one thing, but there are cultural reasons
| too. For example, in Go it is very idiomatic to return an
| error value as a string, which cannot be even imagined of
| in the C, C++, or Rust ecosystem. That's because as Go is
| targetted toward backend-level programming nowadays (which
| was different at the beginning of Go, when Go was marketted
| as a systems language), some minor overhead is acceptable
| if the convenience it brings is worth it. Go is full of
| such compromises, which isn't exactly a bad thing, but
| unsuitable to the lowest level programming which begs for
| an extremist position regarding performance issues. The
| mentality of Go is good enough for conventional
| programming, but bad for systems programming. And I think
| such cultural differences stem from Go's simplicity first
| mindset.
| pjmlp wrote:
| > But I think a nice way to appreciate Go's conservative
| middleground is to go back to writing some C/C++ code for a
| while, like some Arduino projects. Coming from that direction,
| Go feels like a nice incremental improvement that doesn't try
| to do too much (with perhaps the exception of goroutines).
|
| As someone that was writing Arduino like code in C++ in 1993
| (MS-DOS), I fail to see that.
| BenFrantzDale wrote:
| If in 2021 you think of "C/C++" as a single language, you are
| missing out on the last decade of C++.
| pjmlp wrote:
| C/C++ isn't a programming language, rather the abreviation
| for C _and_ C++.
|
| I am fully aware of the last decade of C++, including
| papers written by Bjarne Stroustrup and other C++ key
| figures, where they uses the hated C/C++ expression, that
| so many happen to have issues with.
|
| Which I can gladly point you to.
|
| Given that security is one of my areas, I always keep up to
| date with C and C++ standards, even if I don't use them
| every day.
| c-cube wrote:
| > C/C++ isn't a programming language, rather the
| abreviation for C and C++.
|
| Maybe the abbreviation should be `C & C++` then :-). But
| I'm not sure if infix precedences allow it to work.
| q845712 wrote:
| Agree, agree, agree. I've bounced between Python, C++, and Rails
| in my career, and lately have been ramping up at a Go shop. Go is
| like C++ in the sense I can tell that eventually I'll get better
| at eyeballing a file, sensing the shape of the code, and zeroing
| in on the important bits.
|
| Just like learning to read large C++ codebases, there's clearly a
| learning curve because a simple idea that would be a one-liner in
| a high level language is going to be 5-15 lines.
|
| Go also makes the "learn to read" learning curve even steeper
| when folks pick up and propagate the core team's propensity for
| single letter variables. I feel like one of the understated
| benefits of ruby/python/similar languages being relatively
| readable is that folks are inclined to keep them readable.
| Whereas when you feel like you're one step away from specifying
| the register and how hot the welding torch should be to toggle
| the bits, you're somehow more likely to write
| `c.T.Client.Send(v)`.
|
| IMO if we accept that most of our work is maintenance &
| modification of existing code, and most of the work in those
| tasks is understanding what's already been written, it follows
| immediately that code that's easy to read has high business
| value. I know it's too soon in my learning curve, but I'm really
| hoping I find the patterns that make Go easy to read soon. (and a
| little tangentially but the descriptive power of a variable name
| should be proportional to its scope - so imo anything exported or
| that I might see across multiple files is an awfully strong
| contender for a multi-word descriptive name.)
| thedevelopnik wrote:
| The Go team's recommendation (from Effective Go) is exactly
| what you're suggesting. One letter variables should only be
| used in short functions, a couple lines away from where they're
| created. The further you get from where a variable is created
| the longer and more descriptive its name should be. Some people
| just take it too far or don't read the whole guide I guess.
| benjaminjosephw wrote:
| Learning code shapes and conventions as a skill is definitely
| an important approach for the pragmatic programmer. Ultimately
| though, isn't this a necessary consequence of just not having a
| high enough level of abstraction in our programming languages?
|
| Here's my very opinionated view: in scenarios where
| maintainability and readability are the most important long-
| term characteristics of a program then the best programming
| language would be one which separated out the specification of
| a program's intent from its implementation.
|
| Separating concerns is a design pattern that we've learnt is
| really important (remember the early days of PHP?). We
| currently encapsulate logic around reasons for that code to
| change because we recognize the value of that pattern.
| Extending that same design pattern to what we know about the
| long-term maintainability of code means that we should be
| abstracting intent from implementation to allow both to change
| independently. Compilers and virtual machines already do some
| optimizations to this effect at the moment but we could go way
| further in our programming languages themselves. I think this
| means that declarative programming is the logical evolution of
| software design (really hope someone has an intelligent counter
| argument here because I can't see any myself).
|
| Go makes a great implementation language in this sense (Rust
| too) but it's bound to loose ground in the application space as
| soon as we find the right high-level declarative language to
| describe application software.
| [deleted]
| fmakunbound wrote:
| https://github.com/golang/go/wiki/SliceTricks I was reading
| through this, which was referenced from the article. This just
| seems insane. None of any of that seems intuitive.
| Measter wrote:
| Meanwhile, in the much more complicated Rust, all but four of
| those are one-liners[0]. The exceptions being:
|
| * Expand. Rust doesn't provide an easy way to insert multiple
| values into the middle of a vector, so I had to do 2 lines:
| append, then rotate right a subslice starting at the desired
| insertion point by the length of the insertion.
|
| * Shuffle. No RNG in Rust's stdlib, so I used Rand. I did re-
| implement the shuffle, but in reality I'd probably just use the
| shuffle function provided by Rand.
|
| * In place dedup. Needed two lines: one to sort, then I could
| call dedup.
|
| * Move to front. This is not a function Rust provides, so it's
| completely implemented. Needed 10 lines. This one needs to
| search for the item and move that if it exists. In the event it
| exists, I rotate it left onto the end of the vector, then
| rotate the entire vector right. Otherwise it inserts at the
| beginning.
|
| [0] https://play.rust-
| lang.org/?version=stable&mode=debug&editio...
| Dr_Emann wrote:
| And for move to front, why not `a[..i+1].rotate_right(1);`,
| rather than moving to the back and then shifting everything?
| Measter wrote:
| Uh... because I'd been up for 20 hours and by brain fixated
| on a weird way to do it?
|
| Don't program when tired, kids.
| Dr_Emann wrote:
| Expand does actually exist in the standard library, but it's
| a bit of a strange incantation...
| vec.splice(i..i, std::iter::repeat(0).take(length));
|
| This replaces the values in range `i..i` (an empty range
| starting at i) with `length` copies of `0`.
| Measter wrote:
| That's not exactly the same. Splice returns an iterator, it
| doesn't modify the collection in place.
| cutler wrote:
| Me too. Made me realise I picked the wrong language just for a
| performance boost. The Go patterns mentioned earlier are even
| worse.
| adsharma wrote:
| The idea behind https://github.com/adsharma/py2many is that you
| can still write: del a[i]
|
| and have the go code auto generated. py2many doesn't do that yet,
| but you can teach it to.
| parhamn wrote:
| Most of these will be solved with the recently approve generics.
|
| Concurrency (less the pattern libs that generics will provide)
| feels generally hard. Although channel semantics in Go are very
| annoying (e.g what happens when you close a buffered channel).
| This is hard in most languages I've used. Anyone have an example
| of what the state of the art hard-to-shoot-yourself-in-the-foot
| concurrency looks like in another language?
| cutler wrote:
| Clojure's core/async and Elixir's actors.
| endisneigh wrote:
| I really like Go, but I'd _love_ something like JavaScript that
| had the performance of C^. If and when that happens there really
| wouldn 't be any need for other programming languages IMHO.
|
| Oh well - that being said Go is way simpler than C++18 that I've
| done a tiny bit of work in. I think the issue is that Go
| generally competes with Ruby/Python/JavaScript in certain
| scenarios and just seems way more complex in comparison.
|
| If you compare Go with C++/Java/C# it seems more reasonable and
| "easy."
|
| ^ the good news is that a ton of money is being spent to improve
| Node performance - whether it'll approach Java/C++ speed is TBD,
| but the performance gap is shrinking year by year.
| vitus wrote:
| > C++18
|
| (Out of curiosity, is this C++17 or C++20? I'd assume 17, and
| also I'm curious which aspects you find confusing.)
|
| Several coworkers have remarked that it's just easier to reason
| about correctness of concurrent code in Go vs. C++, while not
| sacrificing performance as in, say, Python. (There's definitely
| a performance hit, per the use of a garbage collector.)
|
| One thing that's always bothered me about Go is just the things
| that are missing from the standard library; I think that
| contributes significantly to it being "harder" to work with.
|
| (A good number of these likely stem from the lack of generics,
| e.g. max(a, b int) int -- math.Max takes and returns floats.)
|
| That said, the biggest thing I liked about Go from the first
| day I used it was the treatment of concurrency as a first-class
| citizen (goroutines, channels).
| Aachen wrote:
| I find Java and C# to be _significantly_ easier than Go. (
| "more reasonable" depends on your goals of course, though if I
| had to pick one language that makes it easy to do things in a
| reasonable way it would be Python.)
|
| C# was one of the first languages I learned after HTML4 and
| Game Maker, no problem.
|
| Java I learned later, no problem.
|
| I also get C++ though I never used it enough to be really
| proficient.
|
| And of course I know a bunch of other languages from awk to
| bash to javascript to php to ruby to sql to vba (not an
| exhaustive list).
|
| But whenever someone makes something in Go, they seem to expect
| that the unique patterns and unusual operators for
| backgrounding tasks etc. to be intuitive to me and I should be
| able to pick up where they left off in a few minutes... it's
| not just not.
| jpgvm wrote:
| I would try Kotlin.
|
| Syntactically it's even more terse than JS and is as fast for
| most use cases as C. Main downsides are memory usage and
| needing to understand JVM limitations for latency sensitive
| codebases (TLDR: Use a modern JVM and ZGC or Shenandoah).
| thu2111 wrote:
| Note that with SubstrateVM/Graal native image, you get far
| lower memory usage and startup time equal to or even better
| than C.
| throwaway189262 wrote:
| > but I'd love something like JavaScript that had the
| performance of C^
|
| Java honestly is close. Data object creation is notably worse
| unless you're using Lombok or similar. Besides that, modern
| Java and JS syntax are similar. And Java doesn't have a
| horrible underlying object model based on prototype chains.
|
| IMO Go seems easy because it's simple. I know Java and JS well
| and they're about the same complexity these days.
|
| Knowing both I will never touch JS for backend again. Java is
| better suited for it. Strong typing is light-years easier to
| follow code paths and refactor. IDE's, debugging, profiling
| tools, memory usage, and performance are all better in
| Javaland. Real threading with proper shared memory exists. App
| size is smaller since Java bytecode is compact and distributed
| zipped.
|
| My company stopped using JS on backend after the hype wore off.
| Our Java backends are easier to monitor and maintain.
| cutler wrote:
| How does Kotlin compare?
| matwood wrote:
| I agree wrt to Java. Because Java has been around for so long
| it has a lot of emotional baggage that I think makes people
| reflexively dislike it. But, modern Java with any of the
| micro frameworks or something like Spring boot, plus jOOQ
| makes for a very easy and productive backend. And because
| Java has been around for so long, if you run into edge cases
| like parsing a weird file format, there is probably a library
| out there. Kotlin has a lot of same advantages, plus a more
| expressive syntax if that's what someone is after.
|
| Go is also works for the backend, but you have to get into
| the mindset that you're probably going to write a lot of
| code. That's fine, but it's a shift from many other
| languages. As someone who has programmed for 20+ years, I
| like the single way to do most things in Go. There's also a
| bit of elegance coming full circle through many languages
| where a for loop is once again just a for loop.
|
| And maybe I'm showing my age, but I also completely agree
| that backend languages need strong typing. The other option
| is significant test coverage, but we have all seen how that
| goes in practice. Code without strong typing always feels
| like it's meant to be written once and thrown away because
| it's so hard to read later. And, this style fits with how UIs
| often work where the life of a product will end up with many
| UIs on the same backend.
| kaba0 wrote:
| There won't be one ultimate language. The difference between
| low-level and high-level languages is significant. High-level
| languages allow you to not have to worry about intricacies of
| memory layout mostly, and thus even changing the data
| structures you use will not result in a refactor. While using
| low level languages, even ones like rust, you can change the
| allocation strategy/memory layout of your data structure, but
| you will need to refact on it. So it is a tradeoff between ease
| of code base change, and tight control on memory/resource
| usage.
| val_deleplace wrote:
| I agree with the 2 example tasks, which are not "simple" to
| write.
|
| The remove-by-value snippet is 8 lines, and it can be wrong in a
| subtle way: if list contains pointers, then we have a memory leak
| beyond the slice final length (pointer values still exists within
| capacity, and objects are not garbage collected).
|
| I wrote several possible implementations here, none of which is
| as concise and as obviously correct as an hypothetical
| "list.remove(v)": https://programming-
| idioms.org/idiom/136/remove-all-occurren...
| nerdbaggy wrote:
| In Go tutorials I almost never see anything about mutexes or how
| to be thread safe
| strken wrote:
| Generally the answer is to stick everything into a channel.
| This doesn't fix race conditions, but it does make it easier to
| write code.
|
| Want to query the db in parallel? Start n goroutines and write
| the results to a channel, then read from the channel n times.
| Want to limit your concurrency to m tasks? Start m goroutines
| reading tasks from a tasks channel and pushing results to a
| results channel, write n tasks to the tasks channel, read n
| results from the results channel.
| eknkc wrote:
| I find the channels to be the most confusing thing in Go.
|
| What happens when you write to a closed channel? What about a
| blocked one? What if you read from a closed one. Can you
| close a closed channel? Does that panic? What was the non
| blocking read? What about write?
|
| Fuck that, I use the sync Mutex primitives and good old
| slices instead.
| Groxx wrote:
| +1 to this. I'd say nearly half of the channel code I've
| encountered in "real code" has had severe flaws when things
| don't align perfectly. They don't have _data races_ , in
| that the race detector will give it a thumbs-up, but they
| do have _semantic races_ leading to deadlocks, misbehavior,
| dropped data, etc. They generally work fine when they 're
| not under heavy load / contention, so they aren't noticed
| until they collapse.
|
| Mutex code though? 90% correct, easily. It's often
| _dramatically_ simpler to make correct, especially when
| more than one construct is needed, or nearly anywhere with
| error handling. (`defer` is quite nice for locks)
| domano wrote:
| In the go trainings i have given i noticed that syntax really was
| never the issue, but some concepts were awfully hard for some
| participants to understand.
|
| Also pointers are a thing people are afraid of for some reason.
|
| On the other hand i notice that most go code i have written does
| not need any maintenance at all, which is neat.
| soheil wrote:
| The fact that Go supports channels, buffers and workers means
| it's very much tailored to parallel processing. I always thought
| of it as a soft of worker farm except it's running on a single
| machine. You can run it in a multi-node environments as well, of
| course. I find the parallelism feature of Go so powerful that it
| overshadows its other features. For server side there are already
| many really well designed systems that allow for parallelism,
| Apache, Nginx, Kubernetes, RabbitMq... For client side
| parallelism maybe useful in things like games but even then there
| are frameworks better suited to take advantage of parallelism
| offered by GPUs. I like Go for command line apps, it's a much
| faster alternative to Python.
| opportune wrote:
| Go is not designed to be a scripting language. It _is_ designed
| to have users think about edge cases and error handling so they
| are sure they are doing what they actually want to. Both the
| slice and goroutine patterns the author uses are pretty much
| standard patterns in Go, like making a factory function in a more
| OO language.
|
| To echo others, when Go makes something cumbersome (like an O(n)
| operation) it's usually to hint to users that they're doing
| something inefficient.
|
| But, agreed you can't pick up Go in minutes. It's best approached
| coming from a background of writing service binaries in C/C++. If
| you aren't familiar with strong type systems and concurrency
| you're going to have problems.
| divan wrote:
| You can't reply to someone's comment on internet about "...could
| pick up Go in 5-10 minutes..." that seriously.
|
| But couple of points about simplicity author missed out:
|
| - deleting from slice is expensive so should be explicit. True
| that lack of user generics prevents people to write own generic
| function for that, but that's not the only thing that generics
| will inevitably bring - are we still talking about complexity?
|
| - Go is still the easiest language to pick up and understand due
| to smaller basis in the feature vector space. That's what Go
| authors mean by orthogonality of features. The search for the
| solution is way more straightforward in Go space - and that's
| what we call a cognitive load.
| marvinblum wrote:
| Agreed wholeheartedly. I professionally progammed in Java, PHP,
| and Go, alongside a few other languages for open-source/pet
| programms (Python, C, C++, ...) and finding a solution to a
| problem in Go is straightforward. When I had to write Java, I
| always had to decide which language feature I'd like to use
| first before getting down to the problem.
| grodes wrote:
| I hope this will change with generics, and we will get
|
| > slices.delete(slice, index)
| labrador wrote:
| Easy to learn doesn't necessarily mean easy to use, which I
| gather is where the "go is easy" meme got started. Morse code is
| easy to learn but hard to use.
| aimatt wrote:
| Some things are done on purpose so that you consciously know how
| expensive of an operation you are doing. In languages like
| Python, it's easy to abuse convenient operations that do a ton of
| computation. Then you have to spent time with valgrind or
| something similar and figure out what the heck is causing the
| code to be so slow.
| zxcvbn4038 wrote:
| Thinking outside the box, maybe people who don't like Go ought to
| try a different language. Give Perl or Ruby a whirl. No memory
| management, no types, no concurrency, nothing to think about,
| just write code. Learn bash and never worry about arrays again. I
| knew a woman who went straight from 6502 assembly to bash. All
| her bash programs had two variables, $x and $y, and she reused
| them throughout. What other language gives you that freedom?
| omginternets wrote:
| This article is built on a false premise: that people reach for
| Go because it is _easy_.
|
| Despite it's flaws (and it definitely has flaws), Go has replaced
| Python as my go-to language, not because it is _easy_ but because
| it is _small_.
| chmod775 wrote:
| We're comparing (linked-) lists with arrays and compiled
| languages with scripting languages now?
|
| Why would you use an array/slice instead of a linked list, but
| then want to use it like a linked list?
|
| If you need a linked list, use something like this:
| https://golang.org/pkg/container/list/
| cwojno wrote:
| I agree with the author that Go is not as easy as Python or Ruby.
| However, the first example given is bad.
|
| Running delete on a slice is the same as trying to delete an
| element of an array. It's the wrong data structure if you need
| that type of access pattern. That's why it's hard. You can cut a
| tree down with a hammer, but it's going to take a while and
| you're going to have some blisters.
|
| Go has built-in linked lists.
| https://golang.org/pkg/container/list/ and I will agree, they're
| not as easy to use as other languages due to the lack of generics
| in go, at present. Using the list.* package requires type
| conversion to evaluate and pull values out of the structure.
|
| I think it's fair to say that Go needs to do a better job
| explaining the list package or possibly including it in the "Tour
| of Go" guide. That might help avoid these misconceptions in the
| future.
| jrpt wrote:
| I feel like the majority of the time you just have arrays or
| slices of a manageable size, say <= 25 elements, and you just
| want to delete an element from it. You don't care about
| performance because it'll be fast regardless and surely not the
| bottleneck of your program.
|
| In my experience with Go, a lot of basic functionality is
| missing and requires you to write your own utility functions.
| And some things you would expect like mySlice.append(elem) are
| instead written mySlice = append(mySlice, elem) which isn't as
| elegant. Plus you have to remember it's not just
| append(mySlice, elem)
|
| If there's a handful of open source utility function libraries,
| that will split the Go developer base, kind of like how you
| used to have both jQuery, Prototype.js, underscore.js and
| others in Javascript. They should have just standardized all
| the common functionality you'd want and in an intuitive way.
| mrmonkeyman wrote:
| People say things like "plumbing" and "utility" functions
| like their code consists of something more, something more
| meaningful.
|
| In Go, there is plumbing, everything is plumbing because that
| is what your job is. It makes it clear and boring. No fun I
| agree, but engineering is no fun.
| masklinn wrote:
| > Running delete on a slice is the same as trying to delete an
| element of an array. It's the wrong data structure if you need
| that type of access pattern.
|
| If you keep deleting elements from arrays then maybe, but if
| you do so once in awhile the cache-friendliness and limited
| allocations overhead of an array will most likely come out
| ahead. Especially if you also want arbitrary access.
|
| Removing an element from a linked list is cheap if you're
| already at that element (assuming the node itself is exposed),
| but most operations are way costlier.
| kaba0 wrote:
| Delete on a not-too-long arrays is pretty cheap. Probably even
| better than iterating over a linked list, but of course it
| depends on the exact application.
| atilaneves wrote:
| If the array fits in the cache, deleting from the middle of an
| array and copying all the subsequent elements one to the left
| is actually faster than a linked list.
|
| And most arrays fit in the cache.
| eternalban wrote:
| Statesments about ease of programming Go are relative.
|
| Go is easy once you internalize its semantics. And this is the
| kicker of Go, it _remains_ simple even after you have achieved a
| level of mastery of the language. This is _not_ the case even for
| a language like Java. Certainly, C++ gurus will have your eyes
| bleed reading their code.
|
| But this is not the case for Go, for the simple fact that Pike et
| al have engineered so that you _can not_ do astronautics with Go,
| without enduring severe pain. So no one does it. Short of Go
| assembler code, any Go code out there is accessible to an
| exceptionally wide range of Go programmers. There is something to
| be said for that.
| d0100 wrote:
| > But I also don't think that Go is a language that you "could
| pick up in ~5-10 minutes"
|
| I disagree. I use Go for our API backend because I can onboard
| junior developers and have them producing very fast. Because they
| are familiar with loops and C-syntax, this is very much a
| guaranteed "pick up in ~5-10 minutes"
|
| The explicitness of Go makes it easy for them to get to work, and
| easier for me to understand what they are doing when it comes to
| code review
|
| They won't ever have to use interface{}, nor concurrency, and
| slice manipulation won't take much time to lookup any "cheat
| sheet", or copy from another file
| klelatti wrote:
| I hope "~5-10 minutes" is shorthand for a somewhat longer time
| periods.
|
| Even so have you considered whether this approach has some
| downsides and risks?
| d0100 wrote:
| Since we just work with mostly simple CRUD, there isn't many
| downsides. Also we use SQL queries for most of our logic, so
| SQL knowledge is the real barrier
|
| I usually ask them to read gobyexample.com, but they can
| start working on projects on the first day.
|
| CRUD project with Go makes it easy to add new endpoints and
| simple features, so we can focus on hiring frontend devs and
| smoothly turning them into "fullstack CRUD devs"
|
| I am sure this can be achieved with other languages, but for
| our use case (software-house) Go just fits really well
| because it provides a low cognitive overhead for juniors and
| even interns to make sense of the project and contribute
|
| It fits pretty nicely in an "assembly line" style
| development, but maybe the downside is that it is boring and
| makes devs want to leave because they feel like they're
| always hitting the mark and "mastered" development and want
| to climb to the next level. It seems reasonable for them, but
| the turnover is tiresome (around 6 months) for me
| klelatti wrote:
| Thanks - interesting perspective.
|
| I do think that we (or me!) hold ourselves to too high a
| standard in terms of mastering all aspects of a language,
| especially given some languages have got too big. It's
| refreshing to hear an approach that focuses on knowing just
| enough to get the job done!
| Philip-J-Fry wrote:
| But no one said Go was easy. It's just simple and easy to reason
| about. There's a huge benefit to something doing exactly what it
| says. No hidden behaviours, no overly complex syntax, nothing
| like that.
|
| I came from C# where I have gotten increasingly frustrated with
| the amount of additions to the language. Like we now have
| classes, structs and records. We now have async/await but we also
| have channels. This is what having a huge toolbox causes. Some
| people are going to write code using channels, me others will use
| async. Some people will use features that others don't care
| about.
|
| I think there's huge benefit in a simple language. In Go I know
| that there's only 2 ways to do concurrency. You either use go
| routines and mutexes or you use go routines and channels.
|
| Generics will bring a lot of helper functions that weren't
| possible before which will remove a lot of repetitive code.
|
| But otherwise I am super happy with writing Go code. All my pet
| peeves of something like modern Java or C# are gone.
| dTal wrote:
| > It's just simple and easy to reason about.
|
| These aren't the same thing. Brainfuck is "simple" in the same
| sense - no hidden behaviors or complex syntax - but it's
| virtually impossible to tell at a glance what a Brainfuck
| program does, or say with certainty how it will react to
| different inputs. Complexity has to live somewhere, and the
| more restrictive the language, the more complexity is moved to
| the program structure. Conversely, every special-purpose
| construct in a language is a place where you don't have to rely
| on reasoning about an unboundedly complex Turing-complete
| system, and can instead go and check the documentation for what
| it does.
| kenniskrag wrote:
| We solve that with communication to use a to the team well
| known subset of the language and internal trainings if we see
| potential helpful features.
| corty wrote:
| > No hidden behaviours, no overly complex syntax, nothing like
| that.
|
| Arrays vs. slices and standard functions dealing with slices
| are full of weird behaviour, unnecessarily complex syntax and
| obscure features. Like, why are there even arrays at all? Which
| slice functions return a copy and which ones don't? Why is
| append() so braindamaged to make a copy sometimes? Why do I
| need make() as often as I do when mostly the language knows all
| make() would do?
|
| Go still has lots of footguns, even right at the trivial
| basics.
| [deleted]
| jerf wrote:
| "Like, why are there even arrays at all?"
|
| Because while Go is garbage collected, it also provides
| modest control over memory layout. Arrays are allocated
| chunks of contiguous memory for holding a fixed number of
| things. Slices are something that sit on top of that and let
| you not worry too much about that.
|
| You almost never want arrays yourself, but they are a
| legitimate use case that the language has to provide because
| you can't create them within the language itself. But the
| right answer for most programmers is to ignore "arrays" in Go
| entirely.
|
| "Why is append() so braindamaged to make a copy sometimes?"
|
| (Tone: Straight, not snark.) If you understand what slices
| are and how they are related to arrays, it becomes clear that
| "append" would be braindamaged if it _didn 't_ sometimes make
| copies. Go is simple, sure, but it was never a goal of Go to
| try to make it so you don't have to understand how it works
| to use it properly.
|
| It is a fair point that quite a few tutorials don't make it
| clear what that relationship is. Most of them try, but
| probably don't do a good enough job. I think more of them
| should show what a slice is on the inside [1]; it tends to
| make it clear in a way that a whole lot of prose can't. "Show
| my your code and I will remain confused; show me your data
| structures and I won't need to see your code; it'll be
| obvious."
|
| [1]: https://golang.org/pkg/reflect/#SliceHeader
| simiones wrote:
| Of course Go has hidden behaviors and overly complex syntax,
| like any programming language before or after.
|
| For example: xrefs := []*struct{field1 int}{}
| for i := range longArrayName { longArrayName[i].field =
| value xrefs = append(xrefs, &longArrayName[i]) }
|
| Perfectly fine code. Now after a simple refactor:
| xrefs := []*struct{field1 int}{} for _,x := range
| longArrayName { x.field = value xrefs =
| append(xrefs, &x) }
|
| Much better looking! But also completely wrong now,
| unfortunately. `defer` is also likely to cause similar problems
| with refactoring, given its scope-breaking function border.
|
| And Go also has several ways of doing most things. Sure, C# has
| more, but as long as there is more than one the problems are
| similar.
|
| And I'm sure in time Go will develop more ways of doing things,
| because the advantage of having a clean way to put your intent
| into code usually trumps the disadvantage of having to learn
| another abstraction. This is still part of Go's philosophy,
| even though Go's designers have valued it slightly less than
| others. If it weren't, they wouldn't have added Channels to the
| language, as they are trivial to implement using mutexes, which
| are strictly more powerful.
| Measter wrote:
| What exactly is the difference here? Is the `x` variable
| being updated in the second sample so that the stored
| references all point to the same item?
| [deleted]
| simiones wrote:
| Yes, that code is equivalent to xrefs :=
| []*struct{field1 int}{} var x struct{field1 int}
| for i := range longArrayName { x = longArrayName[i]
| //overwrite x with the copy x.field = value
| //modify the copy in x xrefs = append(xrefs, &x)
| //&x has the same value regardless of i }
|
| The desired refactoring would have been to this:
| xrefs := []*struct{field1 int}{} for i := range
| longArrayName { x := &longArrayName[i] //this also
| declares x to be a *struct{field1 int} x.field =
| value xrefs = append(xrefs, x) }
| feffe wrote:
| I don't think this is a good example of proving the point
| that Go is difficult to read. Isn't it expected to
| internalize the semantics of basic language primitives when
| learning a new language, or does people just jump in guessing
| what different language constructs do? IMHO you learn this
| the first week when reading up on the language features.
|
| range returns index and elements by value. The last example
| does what you asks of it, it's like complaining something is
| not returned by reference when it's not. Your mistake.
| Perhaps some linter could give warnings for it.
| simiones wrote:
| I don't think you can truly internalize the varying
| semantics of which operations return lvalues and which
| return rvalues. At least, I haven't yet been able to in ~2
| years of Go programming, and it's a constant source of bugs
| when it comes up.
|
| The lack of any kind of syntactic difference between vastly
| different semantic operations is at the very least a major
| impediment to readability. After all, lvalues vs rvalues
| are one of the biggest complexities of C's semantics, and
| they have been transplanted as-is into Go.
|
| As a much more minor gripe, I'd also argue that the choice
| of making the iteration variable a copy of the array/slice
| value instead of a reference to it is the least useful
| choice. I expect it has been done because of the choice to
| make map access be an rvalue unlike array access which is
| an lvalue, which in turn would have given different
| semantics to slice iteration vs map iteration. Why they
| chose to have different semantics for map access vs array
| access, but to have the same semantics for map iteration vs
| array iteration is also a question I have no answer to.
| marcosdumay wrote:
| > and they have been transplanted as-is into Go
|
| Hum... I don't think anything can return an lvalue in C.
|
| Your first paragraph is not a huge concern when
| programming in C, declarations create lvalues, and that's
| it. I imagine you are thinking about C++, and yes, it's a
| constant source of problems there... So, Go made the
| concept much more complex than on the source.
| simiones wrote:
| I think Go has exactly C's semantics here. The following
| is valid syntax with the same semantics in both:
| array[i] = 9 *pointer = 9 array[i].fieldName
| = 9 (*pointer).fieldName = 9
| structValue.fieldName = 9 pointer->fieldName = 9
| // Go equivalent: pointer.fieldName = 9 // you
| can also create a pointer to any of these lvalues
| // in either language with &
|
| Go has some additional syntax in map[key], but that
| behaves more strangely (it's an lvalue in that you can
| assign to it - map[key] = value - but you can't create a
| pointer to it - &(map[key]) is invalid syntax).
| pizza234 wrote:
| I think this is actually a very good example of the inverse
| relationship between logical complexity and language
| complexity.
|
| A language that has implicit copy/move semantics is easier
| to write (since it's less constrained), and more difficult
| to read (since in order to understand the code, one needs
| to know the rules).
|
| A language that has explicit copy/move semantics, is more
| difficult to write (since the rules will need to be adhered
| to), but easier to read (because the constraints are
| explicit).
|
| Although I don't program in Golang, another example that
| pops into my mind is that slices may refer to old versions
| of an array. This makes working with arrays easier when
| writing (as in "typing"), but more difficult when reading
| (as in understanding/designing), because one needs to track
| an array references (slices). (correct me if I'm wrong on
| this).
|
| In this perspective, I do think that a language that is
| simpler to write can be more difficult to read, and this is
| one (two) case where this principle applies.
|
| (note that I don't imply with that one philosophy is
| inherently better than the other)
|
| EDIT: added slices case.
| thayne wrote:
| > But no one said Go was easy.
|
| Lot's of people have said go is easy. In particular that go
| code is "easy to read" is an oft-cited benefit of go.
|
| Also, if you go to golang.org and the first thing you see is:
|
| > Go is an open source programming language that makes it
| _easy_ to build simple, reliable, and efficient software.
|
| (emphasis mine)
| fomine3 wrote:
| simple != easy.
| Philip-J-Fry wrote:
| Go is easy != easy to read.
|
| I personally do think Go is easy to read. The fact that I
| don't get bitten in the ass by hidden behaviours and there's
| no inheritance to step through 5 files of. The fact that
| everyone's Go code looks more or less the same (unless you're
| an absolute beginnner) because there's only so many ways to
| do something. Compared to C# where reading someone else's
| code means I have to take a shot to calm my nerves and then
| take a guess at which permutation of language features they
| decided to use today.
|
| A simple language is easier to read. This doesn't mean it's
| going to be easy for anyone who isn't a Go developer. There's
| no claims of that. But given a week to understand syntax and
| getting used to reading files, I don't think there's many
| places where you'd get stumped.
|
| >Go is an open source programming language that makes it easy
| to build simple, reliable, and efficient software.
|
| This still holds true for me. A simple language builds simple
| software. No noob Go dev is going to have a good time, but
| that's true in almost every language. Once you understand how
| everything works, how interfaces work, how channels work, you
| can simplify down most problems and build them with ease.
| Needing to write a few extra lines of code doesn't mean you
| can't write simple, reliable and efficient software.
|
| Neither of those quotes say Go is an all round easy language.
| It's not like you can throw it at a baby and get back
| Kubernetes. It's not easy to do custom containers, it's not
| easy to do manipulate lists, it's not easy to do a bunch of
| things you'd do in one line in something like C#. But I think
| these are small hurdles for the mental burden you're relieved
| of when you see that the rest of the language is equally as
| simple and benefits from it.
| rswskg wrote:
| I think c# is an unfair comparison in this instance.
|
| You are right, c# does too much. But Go makes it far more
| work to do basic things - list comprehension for example
| (what is everyones obsession with for loops?). An opinated
| language with one way to do things sounds great, but not at
| the expense of basic niceities you get in other languages.
| nmfisher wrote:
| For what it's worth, I'll take for loops over list
| comprehensions any day of the week. I just find it a lot
| easier to read.
| rswskg wrote:
| It's exactly this sort of comment that gives go lovers a
| bad name.
| random5634 wrote:
| Agreed! Check out pythons move with walrus operators
| inside list comprehensions - I think u can do that now -
| starts to be line noise
| simiones wrote:
| So you are claiming this arr2 := []int{}
| for i := range arr1 { if arr1[i] > 9 {
| arr2 = append(arr2, arr1[i] * 2) } }
|
| Is more readable than these? var arr2 =
| arr1.Where(x => x > 9).Select(x => x*2); var arr2 =
| from x in arr1 where x > 9 select x*2; arr2 = [x *
| 2 for x in arr1 where x > 9] (setf arr2 (loop for x
| across arr1 when (> x 9) collect (* x 2)))
|
| I honestly can't imagine by what measure any of the
| latter ones could be harder to understand the former. And
| as the complexity of the expression increases, I only see
| the advantage increasing typically (though often it pays
| to split it into multiple comprehensions, just like a
| loop that does too much).
| Nullabillity wrote:
| As someone who reads a lot more Go than I write, Go is
| awful to read.
|
| Reading Go means that I keep having to read idioms (as
| posted in the article) and parse them back into the actual
| intent, rather than just reading the intent directly. But I
| can't even trust that, since the nth rewrite of the idiom
| might have screwed it up subtly.
|
| Reading Go means that I can't easily navigate to the
| definition of a function, because it implicitly merges
| folders into a single namespace, so I have to try them one
| by one.
|
| Reading Go means that I usually don't have tooling that can
| jump to definition, because there are umpteen different
| dependency management tools, and Gopls only supports one of
| them.
|
| Reading Go means that even when I have tooling available,
| _and_ it feels like working today, jump-to-definition
| becomes impossible as soon as interfaces are involved,
| because structural typing makes it impossible to know what
| is an intentional implementation of that interface.
| sagichmal wrote:
| > Reading Go means that I keep having to read idioms (as
| posted in the article) and parse them back into the
| actual intent,
|
| What? I never have to do this...
|
| > I can't easily navigate to the definition of a function
| . . . I usually don't have tooling that can jump to
| definition
|
| What? You command-click on it... ?
|
| > jump-to-definition becomes impossible as soon as
| interfaces are involved, because structural typing makes
| it impossible to know what is an intentional
| implementation of that interface
|
| What? You obviously can't jump-to-definition of an
| interface, but in what way is this a limitation?
| Philip-J-Fry wrote:
| >Reading Go means that I can't easily navigate to the
| definition of a function, because it implicitly merges
| folders into a single namespace, so I have to try them
| one by one.
|
| Not sure what you mean here
|
| >Reading Go means that I usually don't have tooling that
| can jump to definition, because there are umpteen
| different dependency management tools, and Gopls only
| supports one of them.
|
| There's 1 dependency management tool though. It's go
| modules. Unless you live 2 years in the past?
|
| >Reading Go means that even when I have tooling
| available, and it feels like working today, jump-to-
| definition becomes impossible as soon as interfaces are
| involved, because structural typing makes it impossible
| to know what is an intentional implementation of that
| interface.
|
| You just right click in VS Code and click "Go to
| implementations". Or if you use Goland it's right there
| on the gutter.
|
| Reading comments like this really makes me wonder if
| anyone complaining about Go has actually used it.
| Nullabillity wrote:
| > Not sure what you mean here
|
| In Rust, if I see a call to `foo::bar::baz()`, I
| instantly know that `foo/bar.rs` or `foo/bar/mod.rs` (and
| only one will ever exist) contains either a `fn baz()`
| (in which case I'm done) or a `pub use spam::baz;` (in
| which case I can follow the same algorithm again).
|
| In Go, if I see a call to `import ("foo/bar");
| bar.baz()`, all I know is that ONE of the files in the
| folder `foo/bar` contains a function `baz`, with no
| direction on which it is.
|
| > There's 1 dependency management tool though. It's go
| modules. Unless you live 2 years in the past?
|
| As I said, 99% of my interaction with Go is reading the
| code that other people wrote. I don't make the decisions
| about which dependency management tools they use.
|
| > You just right click in VS Code and click "Go to
| implementations". Or if you use Goland it's right there
| on the gutter.
|
| I use Emacs, but VS Code still uses the same broken
| Gopls.
|
| > Reading comments like this really makes me wonder if
| anyone complaining about Go has actually used it.
|
| Can't really say I disagree, I guess.
| sangnoir wrote:
| > In Go, if I see a call to `import ("foo/bar");
| bar.baz()`, all I know is that ONE of the files in the
| folder `foo/bar` contains a function `baz`, with no
| direction on which it is.
|
| This is a code smell for poor file organization in your
| "foo/bar" module - in a well organized project, it should
| be obvious which file in a module contains a given
| function[1]. Go doesn't force file=module paradigm
| (preferring the folder to be the basis), however, it
| doesn't _preclude_ having one file per module, if that 's
| what you prefer. If you're reading someone else's poorly
| organized code, you can always use grep.
|
| 1. Say, you have an `animal` module, the first place you
| check for `NewCow()` is `animal/cow.go`
| thayne wrote:
| Like the parent, I read more Go than I write, and I have
| never seen a single-file-per-module (unless the entire
| project is one file). And sometimes it's obvious what
| file a function is from (like in your examples), but a
| lot of times it isn't. For example is `GiveFoodToCow` in
| food.go or cow.go? Maybe there are patterns that
| experienced gophers know, but that adds cognitive load.
| And would having a 1 file per module paradigm have made
| go any more complicated?
| sangnoir wrote:
| food.go and cow.go do not belong in the same (sub)module,
| IMO. That said, each team (and project) have unique
| sensibilities - consistency and familiarity help here. My
| gut feeling is that the "belongingness" of feeding a cow
| is closer to the cow than the food, unless your food.go
| is full of "GiveFoodToCow(),
| GiveFoodToChicken(),...GiveFoodToX()" which is gross.
| With that complexity, you're better of with a Feeder
| interface and implement Feed() in cow.go (and chicken.go,
| etc). If you cannot distill the logic to a single
| interface, you're probably better off with a receiver (or
| regular) function in cow.go, because having a list of
| GiveFoodToX() with different args in food.go is the worst
| possible design you could go with.
|
| > And would having a 1 file per module paradigm have made
| go any more complicated?
|
| I was being facetious. Under normal circumstances, no one
| should use one file per module in Go, but if Rubyists are
| feeling home-sick while writing Go, the option is
| available to them ;)
| Nullabillity wrote:
| > Go doesn't force file=module paradigm (preferring the
| folder to be the basis),
|
| Yes, this is what that complaint was about?
|
| > however, it doesn't preclude having one file per
| module, if that's what you prefer.
|
| > 1. Say, you have an `animal` module, the first place
| you check for `NewCow()` is `animal/cow.go`
|
| This whole subthread was about reading other people's Go
| code. Of course your own code is going to look organized
| to yourself!
|
| > If you're reading someone else's poorly organized code,
| you can always use grep.
|
| Yeah, ripgrep tends to be what saves me in the end. Still
| annoying to have to break it out all the time.
| sangnoir wrote:
| > Yes, this is what that complaint was about?...This
| whole subthread was about reading other people's Go code.
|
| So the complaint, restated is "Go doesn't prevent other
| people from writing bad code?" Am I getting you right? If
| so, well, I have nothing to say about that.
|
| edit: I do actually have something to say. I just
| remembered having to work with an 82,000-line long Perl
| module file that would defeat any IDE. Fun times. No
| language can save you from poorly organized projects,
| whether the modules are file-based or folder-based.
| Nullabillity wrote:
| I would say it's closer to "Go doesn't do a really simple
| thing that nudges people towards writing more readable
| code, while having basically no tradeoffs".
|
| Considering the far more tedious tradeoffs that Go _does_
| force on users in the name of supposed readability (for
| example: the lack of generics), I 'd consider that a
| pretty big failure.
|
| I don't expect them to be perfect. I do expect them to
| try.
| sangnoir wrote:
| FYI: Go's lack of generics was not related to readability
| - the Go team didn't have a solution they liked, so
| instead of saddling the language with a half-assed
| solution forever, they waited for a more elegant
| solution. Also: the design of Go generics was recently
| approved (as in the past month).
|
| It's no secret that Go is targeted at "programming at
| large". Go's design favors larger, fewer modules, how
| those modules are structured is left to
| teams/individuals. I may be lucky to work with a great
| team, but I always find the code where I expect to find
| it. When I'm starting from scratch, I utilize modules,
| <$VERB>er interfaces, structs and receiver functions: I
| cannot remember ever running into an ambiguous scenario
| where I'm unsure where a particular piece of code ought
| to go.
| fxtentacle wrote:
| Let's go with "Go is easy if you use an appropriate IDE"
|
| Emacs is great for many things, but Go was clearly
| designed with GUI IDE tooling in mind. So if you decide
| to forfeit that huge benefit, of course it'll be a sub-
| optimal experience.
| psanford wrote:
| Gopls works great with Emacs.
| prussian wrote:
| What makes Emacs not a GUI IDE?
| morelisp wrote:
| > Go was clearly designed with GUI IDE tooling in mind.
|
| I would like to see what happens if you say this to Rob
| "syntax highlighting is for children" Pike's face.
| cy_hauser wrote:
| > Go was clearly designed with GUI IDE tooling in mind.
|
| Surprisingly not. One of Go's authors commented many
| times in the early days that Go shouldn't need an IDE,
| only a text editor. He even commented that syntax
| highlighting/coloring were unnecessary distractions.
| fxtentacle wrote:
| He said:
|
| there was talk early in the project about whether Go
| needed an IDE to succeed. No one on the team had the
| right skill set, though, so we did not try to create one.
| However, we did create core libraries for parsing and
| printing Go code, which soon enabled high-quality plugins
| for all manner of editor and IDE, and that was a
| serendipitous success.
|
| Which I read as they considered IDEs important.
| simiones wrote:
| To be fair, we then had to throw all of those "high-
| quality" plugins in the garbage where they belonged, and
| go with Microsoft's Language Server architecture instead
| to really have a usable Go IDE experience in something
| like Emacs or vim (GoLand from JetBrains is of course
| much better, and that might be using some of the built-in
| Go tools?).
| cy_hauser wrote:
| I'll accept that as a reasonable conclusion. Then he also
| said the following:
|
| https://usesthis.com/interviews/rob.pike/
|
| https://groups.google.com/g/golang-
| nuts/c/hJHCAaiL0so/m/kG3B...
|
| (and some other stuff about syntax highlighting from
| around that time (?) I couldn't find.)
|
| So maybe it was a bit of both.
| flippinburgers wrote:
| I happily use golang in vim using gopls. I don't see an
| IDE being a requirement at all.
| tankenmate wrote:
| The first one is an issue, not just for reading but also
| for writing; you'll find slightly similar ways of doing
| the same thing, and Murphy's (Real) Law kicks in "If
| there are two or more ways to do something, and one of
| those ways can result in a catastrophe, then someone will
| do it."
|
| The rest of the issues seem to be tooling issues, not an
| issue with Go itself. There are tools out there that do
| the right thing (personally I'm old school and use
| gotags, warts and all), so I'd suggest get better tools
| that work for you (or write one if you can't find one you
| like).
| [deleted]
| joelfolksy wrote:
| "A simple language is easier to read." "A simple language
| builds simple software".
|
| It's become a cliche to bring up brainfuck here, but it
| really is a direct refutation of these ideas, being a
| maximally simple language that produces maximally
| complicated code.
| nvarsj wrote:
| Go _is_ easy to read. I can read almost any go codebase, even
| giant ones, and see what's going on pretty quickly. Same with
| C - the Linux kernel is surprisingly easy to understand once
| you know the basic data structures it uses. There is a lot of
| benefit in using simple/limited languages from a readability
| point of view.
| owl57 wrote:
| The data structures in C require thorough documentation to
| have any chance to keep your foots. And keeping the
| preprocessor wizards in check. But I agree that Linux
| authors have done a consistently good job.
|
| In fact, a kernel is even easier to read than other
| programs whose authors worked as hard, because having no
| external libraries at all also helps.
| johannes1234321 wrote:
| Reading the kernel is relatively easy since some people
| worked hard on a structure, which is relatively clean.
|
| There are many macro C code bases with tons of function
| pointer fun (for example when doing OOP) where you have a
| hard time to find things, till you spent considerable time
| learning the choices made in that code base.
| toolz wrote:
| Maybe for small pieces of code, but getting the overall
| higher level feature goals of code in go is hell (for me).
| The lack of function overloading to generalize intent for
| generic work is abysmal and outdated. It pains me to no end
| that their built in functions are overloaded, but I can't
| do it in my own go code.
|
| Generally speaking if you're modestly familiar with a
| language it's rare that it's difficult to read small pieces
| of code. The hard part of programming on a team is writing
| code such that high level intent is quickly apparent with
| APIs that support that intent and make it easy to
| understand where edge-case handling is happening vs direct
| feature intent
| baby wrote:
| Go is easy to read
| threatofrain wrote:
| But for most programmers, easy outweighs simple in the
| hierarchy of values.
| felipellrocha wrote:
| Which is a problem. (Though, i will say i think go is neither
| easy nor simple, but i believe that is an unpopular opinion
| here at HN)
| [deleted]
| lima wrote:
| Simple > easy should be common sense for anyone who ever
| worked in a team. Code is written once and read many times
| (and usually not by the author).
| DennisP wrote:
| I don't entirely disagree but I can't help noticing, you
| mentioned two ways to do concurrency in C# and two ways to do
| it in Go.
| sigzero wrote:
| I am pretty sure I have read something similar to "Go is easy"
| a multitude of times.
| callmeal wrote:
| >This is what having a huge toolbox causes. Some people are
| going to write code using channels, me others will use async.
|
| I don't know if this is a problem. Redundancy is good. If
| simple languages ruled, we would all be speaking Esperanto.
|
| >Some people will use features that others don't care about. I
| end up having this argument with product managers who insist
| that because most people only use 60% of the functionality of a
| product, we don't need to implement the remaining 40%. You need
| more than one way of doing the same thing.
| sagichmal wrote:
| On the contrary, every instance where there is more than one
| way to do something is a failure of the language. It's OK,
| all languages have failures, but the ideal is pretty clearly
| a set of precisely orthogonal features, where there is
| neither repetition nor exception.
| rednum wrote:
| Actually Rob Pike said that Go was supposed to be "easy to
| understand and adopt":
| https://www.youtube.com/watch?v=uwajp0g-bY4
| BenFrantzDale wrote:
| This is the same guy who somehow thought a half-speed
| language without generics was a somehow going to be a C++
| competitor.
| mbesto wrote:
| > But otherwise I am super happy with writing Go code. All my
| pet peeves of something like modern Java or C# are gone.
|
| I'm always confused by this. Why do people consistency cherry
| pick specific languages to compare then when those languages
| offer fundamentally different paradigms? I understand comparing
| C/C++ vs Go, but isn't obvious that C# is going to be
| fundamentally different than Go?
| Agingcoder wrote:
| Yes it's obvious, but it doesn't prevent OP from liking go
| more than c#/java.
|
| Furthermore, its very likely that the languages people
| 'cherry pick' are actually languages they use everyday. So
| they just compare 'new everyday language' to 'previous
| everyday languages', and just tell you whether they're
| happier or not when they write code. The point is not really
| about the language theoretical properties and merits, but how
| people experience the language in real life.
| simiones wrote:
| C# and Java are much closer to Go than C++. C is also closer
| from another perspective (simplistic syntax).
|
| C++ is about as far from Go as you can imagine a language.
| Maybe only Prolog would be a worse comparison point.
|
| C++ is designed with one goal in mind: no special compiler
| built-ins. If it is possible to do it at all, it can be done
| in a 3rd party library. C++'s design philosophy is Library
| first. Bjarne Stroustroup explicitly advocates this: don't
| write special case code. Write a library that solves the
| problem, then use that library to achieve your special case.
|
| Go doesn't think writing libraries is a useful endeavor for
| most programmers: they should be writing application code,
| and let their betters design the tools they need, build them
| into the compiler, and stop wasting time designing your own
| abstractions.
| mbesto wrote:
| > C++ is about as far from Go as you can imagine a
| language. Maybe only Prolog would be a worse comparison
| point.
|
| https://commandcenter.blogspot.com/2012/06/less-is-
| exponenti...
|
| /headscratch
|
| EDIT: I think you're misinterpreting syntax of the language
| with use case. e.g. low level vs high level programming
| language
| simiones wrote:
| I'm not talking about either syntax nor use case, but
| about language philosophy, the approach to problem
| solving embodied in the language - the "paradigm" as you
| called it. As that post points out, even inside Google,
| the C++ committee members had a vastly different vision
| than Rob Pike on what makes a language good. C++11 is
| essentially universally loved in the C++ community as a
| giant step forward, as are most subsequent revisions. Rob
| apparently hates them.
|
| Even the article shows that Go and C++ are fundamentally
| at odds philosophically, in terms of their paradigm:
|
| > Jokes aside, I think it's because Go and C++ are
| profoundly different philosophically.
|
| > C++ is about having it all there at your fingertips. I
| found this quote on a C++11 FAQ:
|
| > The range of abstractions that C++ can express
| elegantly, flexibly, and at zero costs compared to hand-
| crafted specialized code has greatly increased.
|
| > That way of thinking just isn't the way Go operates.
| Zero cost isn't a goal, at least not zero CPU cost. Go's
| claim is that minimizing programmer effort is a more
| important consideration.
|
| If you want to compare them on use cases, Go is far too
| slow and memory hungry to be used in most domains where
| C++ is actually necessary. That is, for any C++ program
| that could be re-written in Go, you could also re-write
| it in Java or C# or Haskell, bar a few problems around
| startup times and binary sizes.
|
| And that is what has been seen, again as the post points
| out: C++ programmers are not switching to Go. Ruby and
| Python and (Java and C#) programmers are.
|
| Also, the designers of C++ have never made any effort to
| think about how easy the language is to learn, unlike the
| designers of Go, Java and C#. In fact, all of these
| languages share a common heritage: they are all designed
| as answers to C++, in slightly different ways.
| Hawxy wrote:
| > Like we now have classes, structs and records.
|
| IMO records are a great addition, especially when it comes to
| point-in-time/immutable data. They also provide an improvement
| to developer efficiency, as basic data classes can be
| represented with a single line record definition.
|
| > We now have async/await but we also have channels. Some
| people are going to write code using channels, me others will
| use async.
|
| I'm not really sure what you're talking about here. C# has no
| concept of a channel. If you referring to the
| "System.Threading.Channels" package, it exists for those in the
| community that would benefit from it, and still uses familiar
| async/await syntax. It's also a very niche package that is
| unlikely to have significant adoption, so there's no real
| concern of the pattern "segmenting" the community.
| netmonk wrote:
| OP complains that GO official tutorial is all about syntax. So
| what are the ressources to learn GO specific mindset ? More
| generally, that was my concern back in the day with "The C
| programming language" which was only about how to write C code
| but not how to think in C. Which is quite different !
|
| What are the best ressource online about how to think in a
| language (RUST/GO/C/C++/JAVA/JS/PYTHON/HASKELL/CAML...) ? Not
| only limited on about to syntaxically write a correct program !
| marvinblum wrote:
| Tutorials on source code structure and open-source projects.
| mraza007 wrote:
| After reading this article now I'm questioning my self should i
| continue learning Go as this is giving me second thoughts that if
| it's going to be useful for me to learn go
| arp242 wrote:
| I could write a "ugh, this is kinda annoying"-criticism of any
| language I've ever used in depth. I certainly didn't intend
| this to be a "Go sucks!" kind of post: as the author of this
| article, I still use (and like!) Go very much, and is pretty
| much the language I use by default for most problems.
|
| And even if it turns out you don't really like Go: that's okay.
| The only way for yourself is to actually learn it, and you will
| be more knowledgable and experienced regardless.
| marcus_holmes wrote:
| > "I've seen many people coming from Python or C# try to shoehorn
| concepts or patterns from those languages in Go. Common ones
| include using struct embedding as inheritance, panics as
| exceptions, "pseudo-dynamic programming" with interface{}, and so
| forth. It rarely ends well, if ever."
|
| I've seen this so much. I did it too. You can almost watch people
| go through this process in places like r/golang.
|
| We all start off coming from a different language and being used
| to that, and while Go takes about an hour to learn all the
| syntax, it's only then that the learning starts. We all then try
| to write code the way we've always written it, only to find that
| Go doesn't work like that. Then we bump into the "this standard
| lib logging library is so shit, let's write a better one" (for
| the more ambitious, this is a whole framework). Then we learn
| more, and begin to realise why the logging lib is like that, and
| why there's no need for some kind of middleware construct, and so
| on. I'm not sure where this journey ends, because I haven't
| finished it yet - I suspect my understanding of chans and
| goroutines is incomplete because I still think they're "a bit
| clunky".
|
| Simple is hard. But good.
| majewsky wrote:
| My favorite way to summarize this is "some people can use any
| language to write Java" (or C or Python or Haskell etc.).
| SEJeff wrote:
| FWIW, the generics proposal was accepted:
|
| https://github.com/golang/go/issues/43651
|
| This will make a generic "slices" package much more trivial to
| write.
| CameronNemo wrote:
| Error checking/handling is still a pain. Maybe generics will
| make that easier? Result type when?
| tptacek wrote:
| How does a Result type making error handling _easier_? More
| reliable, sure, I buy that. But I write a bit of Rust these
| days, and error handling is definitely not _easier_. It 's
| actually kind of an enormous pain in the ass, and it
| certainly seems to involve more lines of code than it does in
| Go (not just in my code, but in all the libraries I've read).
| lmm wrote:
| The most common thing you want to do with an error is
| bubble it up. IME Result types strike the least-bad
| compromise between making that too intrusive (if err != nil
| clutter) and making it too subtle (exceptions i.e. magic
| invisible control flow).
|
| (I don't think Rust is the best example of result types;
| it's missing a lot of tools for working with them because
| Rust doesn't have HKT and functions aren't quite first-
| class because of their lifetime checker, so instead of
| using ordinary functions you end up using ad-hoc macros or
| language special cases like ?. Since Go is garbage
| collected they wouldn't have that problem)
| jjnoakes wrote:
| I've not had that problem. I would love to see the question
| mark operator in go because I think it is an elegant way to
| avoid a ton of boilerplate while still remaining a tad
| explicit about where errors can occur.
| ampdepolymerase wrote:
| Hasn't there been several error handling proposals already?
| kyrra wrote:
| Yes, and all have been rejected thus far. Finding the right
| balance for those things seems to be hard.
|
| See:
|
| Problem statement: https://go.googlesource.com/proposal/+/m
| aster/design/go2draf...
|
| Proposal one: https://go.googlesource.com/proposal/+/master
| /design/go2draf...
|
| Proposal two: https://go.googlesource.com/proposal/+/master
| /design/32437-t...
| Pxtl wrote:
| Finally! I honestly think that any statically-typed language
| without some minimal facility for generics - even C macros will
| do - isn't fit for purpose. I used Java and C# before the
| generics were introduced and you can't pay me enough to go back
| to that.
___________________________________________________________________
(page generated 2021-02-22 23:02 UTC)