Why I Don't Like Golang (2016)(teamten.com) |
Why I Don't Like Golang (2016)(teamten.com) |
However, the issue this brings up about structs / types not explicitly declaring which interfaces they implement is a real and unaddressed problem, especially in large codebases. The only tool that I'm aware of that finds implementations is GoLand, at steep JetBrains prices.
Figuring out what type an API is asking for should not require reading every line of code in the package, and slows down every developer of large Go projects
Over three years that's like $150-$200 total and it will save you so many headaches. But that's a steep price? Are you kidding? Why do developers hate tools that cost money when they save them time and allow them to do more?
A language that depends too much on IDE integration for usability is a real problem, because now you have tool fragmentation as everyone goes different routes with varying levels of success to fix the deficiencies in your language. In the end you end up rolling your own tools as I have done, which is the absolute WORST of all worlds.
Go was supposed to be simple, but all it succeeded in doing is shifting the complexity elsewhere and calling mission accomplished. When you're designing a language, it's VERY important to understand the difference between the emergent complexity of the domain, and the inherent complexity of your design. The latter can be fixed, the former can only be managed - in ways that are already well researched (or just swept under the rug, as go has done).
Too much magic and too much "clever" re-purposing of existing paradigms (file names, capitalization, implicit contracts, etc) makes for an infuriatingly bad design.
I like working with Free and Open software much more than proprietary software. I think it's important for society, and I have more fun that way too!
Also the payoff for me has been very good, I can learn emacs once and enjoy using it for the rest of my life for all significant written language tasks on a computer.
Perhaps I could be a little more efficient if I were using a jetbrains IDE, but then I wouldn't like what I was doing as much. Enjoying what I do, even if it may look slightly contrived to others, is important in me achieving results at work.
But I'm not sure any IDE is worth $90/year when VS Code is free. The extensions for VS Code are next-level, especially the SSH extension. No other IDE comes remotely close to how well that extension works for its use case.
You can do this with a line of code below the struct definition, something like:
var _ <interface> = &<struct>{}
The compiler will also generate helpful errors if the struct doesn't implement the interface.Not requiring struct definition is a great feature in golang. I'm able to add an interface to a struct defined in another library to inject a different implementation for unit tests.
No, it's not. It's one of the best features, and I wish every language did this. At least TypeScript does it, too.
When would you want to look for all implementations of an interface? Is this something like an abstract syntax tree?
I’ve developed large Go codebases and never had this problem so your last sentence is false. In addition this is not an issue other go developers I’ve spoken to have ever worried or talked about.
Isn't it a feature? You can have a "writer" as long as it can "write", and then use that writer anywhere where a function expects a writer?
So are you trying to find implemented interfaces or interfaces' implementors?
Former: In Vim I can use `:GoImplements`, which internally calls `guru` I guess.
Latter: `gopls` supports this.
I agree it's still a pain that one can not tell directly from code what interfaces a struct implements tho.
Alan Donovan's guru tool could do this, but it kind of broke with modules and it was never updated and deprecated in favour of gopls. I don't know if gopls added this yet (I never really found a use for it).
I don't think it's very hard to write a tool for this though; parsing Go code is fairly easy and the stdlib provides a decent API for it. I think you could have a functional tool in a day if you wanted to, although without any caching it might be a little bit slow on larger code bases.
type Bar interface {
BarMethod(int, int) int
}
type Foo struct {}
// Error: Foo does not implement Bar (missing method BarMethod)
var _fooImplementsBar Bar = Foo{}Firstly You can literally just use one of the dozens of go lsps or code tools to search for API invocations to find what structs are passed/called into it. More importantly if you need to know you've written bad code. The entire point of an interface is that you SHOULDN'T need to know the underlying type. If you do you've violated the entire point. Just pass concrete ones. I've written Go for years and never had a problem with this, even in large open source projects like Kuberenetes.
Secondly, the criticism about flipping return values order/meaning isn't a criticism of interface being structurally typed (https://en.wikipedia.org/wiki/Structural_type_system). If you return int, int and the second int "should be even", you should have defined a type "Even" and returned int, Even*. Systems which are structurally typed can demonstrate functional extensionality and (https://github.com/FStarLang/FStar/wiki/SMT-Equality-and-Ext...) and check whether you've flipped the arguments, which would be a more valid criticism (but such checks are expensive and conflict with compile time requirements). Also Java has the same problem, if you define two interfaces with the same method signature and a single class implements both you can't disambiguate.
Thirdly, the structural typing has a huge advantage, namely looser coupling and more tightly defined interfaces. If you follow the "accept interfaces return structs" go idiom, you'll see why. An open source library that does so leaves their returned structs open to be used by consumer code, that itself uses interfaces, without modification required. This means most go code has small, tightly defined interfaces, where every function on the interface is invoked in the relevant function.
For example if you have a library with this definition:
type Baz struct {}
func (b Baz) Foo(){} func (b Baz) Bar(){}
I can use Baz in my code like so:
type Fooer interface { Foo() }
func DoSomething(f Fooer) { }
And use the underlying library, while being decoupled from it, without having to modify it.
Fourthly: You can explicitly say a type implements an interface...
* A good study: https://blog.boot.dev/golang/golang-interfaces/
* In Idris we would do:
even : Nat -> Bool even Z = True even (S k) = odd k where odd Z = False odd (S k) = even k
int -> even doubler a = 2 * a
To be fair, most APIs need documentation and code is just plainly not enough. At least if we are talking about specialist interfaces that aren't just another web framework.
2019, 296 comments - https://news.ycombinator.com/item?id=20166806
2018, 148 comments - https://news.ycombinator.com/item?id=16414098
2016, 47 comments - https://news.ycombinator.com/item?id=12356823
Agreed! https://pkg.go.dev/sort#Slice is wonderful. (Added a bit after this article was written, I think.)
sort.Slice(people, func(i, j int) bool { return people[i].Name < people[j].Name })
In Python one might write: people.sort(key=lambda person: person.name)
Or in Rust: people.sort_by_key(|person| person.name); // sort_by is also an option...
I think it's worth calling out exactly what is happening in the Go example:- We create a closure that captures the people slice
- We pass the people slice and the closure to the Slice function
- The Slice function mutates the people slice, and because the closure captured the slice it sees these mutations too
I get why the Go team wrote sort.Slice like that, and it was perhaps the best they could have done with the language features...But I think we're going to have to agree to disagree on how wonderful it is compared to other languages ;).
I’m looking forward to generics improving this too. (e.g. https://github.com/golang/go/issues/47619#issuecomment-91542...)
If I need to reverse the order, it looks easier to do with Go (just reverse the operator) than with Python and Rust way (I guess both have something like an "order" additional parameter).
Rust and Python both feel more elegant but I actually like Go's way.
Sorting by key is a special case (admittedly the most common special case).
1. Probably a matter of taste, but I love this feature, just because of the lack of noisy public/private keywords everywhere that you see in Java et al. It also means you can tell from a usage (not just the definition) that something is exported, which is often useful.
As far as renaming goes, either rename the definition and see where the compiler complains, or get your IDE to do it (I use GoLand, but good things are said about gopls).
As for his example, the idiomatic way to write that is either just call it `usr` or `adminUser`, or use `user := &user{}` which is valid (if a little confusing).
2. This is a feature: it allows you to define interfaces only where you need them (on the consumer side), and you define the interface with only the methods you actually need. This means that when you add a bunch of new methods on the implementation, you don't need to change all the consumers. Go interfaces are amazing.
The downside he discusses almost never happens: it's surprising, but even in large projects I've never had structs accidentally implementing interfaces or a IsAdmin method being implemented with reversed polarity by accident.
3. Definitely has its downsides. Tooling helps find unchecked errors. Though I've found the biggest downside to explicit errors is the verbosity. You do get used to it, and the explicitness is at least clear.
4. There are a couple of "magical" things like this, but they're well known and documented, and simple to fix if you run into them. I love the fact I can just name a file foo_test.go and add TestFoo methods, and "go test" finds them automatically.
5. I have not found this to be the case, and in the rare cases it does happen, the compiler tells you loudly and it's easy to fix.
6. Yeah, this is a slight pain, but the semi-official "imports" package (golang.org/x/tools/imports) fixes it up, so you just run generated code through that (and it auto-formats the code as well). It's a couple of lines of code. See: https://github.com/benhoyt/prig/blob/2df1b65a2bdf34c10bb5e57...
7. Yeah, I wouldn't mind a ternary operator. Easily misused, which is why they didn't add it, but it would be really nice used judiciously, rather than the 4-line if-else block.
8. Fixed by sort.Slice, which avoids the need for Len and Swap (and even more so by the new generics "slices" package, coming soon). I guess this was added after the article was written?
9. Fixed by "Go modules", which is really well designed and works well (though opinions differ).
10. Fixed with generics being added in Go 1.18. And generic helpers like "slices" and "maps" packages coming soon.
11. Yeah, slightly annoying for newbies, though as he mentioned, tooling tells you. I do like the control you (can) get over allocation and memory management with Go slices.
As far as his summary goes (eg: the type system getting in your way for large programs), I have definitely not found that to be the case. The author doesn't like Go, and that's okay! I don't like Java. :-)
If I want to change the contract of get_kittens so that it returns a set instead of a list, I found it quite tiresome to then go to all the call sites of get_kittens and change their types to match.
What was I doing wrong?* Perhaps there’s a cleverer tool out there that can infer and implement these type changes for me, automatically?
* using vim? [joke]
1. Use `:=` whenever you can, so types are inferred by the assignments/allocations 2. Alternatively query all references from `gopls` and put the results into quick fix list, then `:cdo`
My take is that Go is basically the new Java, with fewer abstractions and faster compilation. Although, the pre-Java 8 Java, before Java started to get a bit functional.
Like Java it’s a practical, imperative, statically typed, garbage collected language with very good performance. Also like (pre-Java 8) Java, it’s very verbose, doesn’t allow for much “elegance”, and many find it not very fun to write. But it is a pretty decent language for getting shit done.
Overall, I don’t really enjoying writing Go, but it’s not the worst either. I’d code in it if necessary, but wouldn’t chose it for a personal project. I just have more fun and am more productive writing code in concise, mixed OOP/FP languages like TypeScript or Scala, even if they don’t compile as fast.
After such public disregard to the communnity and contributors as a whole, the talk about good or bad has no meaning until they learn the basics. For example how to work with community, and the fact that you have to provide your phone number in order to fix urgent bug or implement some feature is a plain stupid(or rather malicious).
Just imagine you've spent your free time working on the fix or feature, and instead of getting appreciation or sometimes bounty or just nothing, you're being "charged" to contribute. Yeah, they really think it's normal that contributors have to give up PI to the advertisement company that were accused of violating privacy many times before. They basically treat contributors, tech-savvy users who provide free labor, like their usual consumers. Just think about it for a second, this is insane.
one particular thing that tells that is the attitude to interfaces:
while in java (and most languages) interfaces are used to tell which contracts a class implements, in go it’s reversed. you must declare interfaces to _require_ certain contracts, for arguments in your functions
for example:
type interface Operator { Operate(int, int) int }
func IntOparation(a, b int, op Operator) int { return op.Operate(a, b) }
this is a major difference highlighting the ownership boundaries: * when I write a package and rely on a 3rd party contract, instead of referencing it and adhering to it, I will copy-paste parts that I need to my package and be independent
What the fuck?
I know Go isnt great or perfect, but still, why so much hate? I seriously want to know
Yes, because it is easy and predictable to track exceptions in nested try catches and hidden control flow.
And everyone agreed so hard that it was removed from almost every modern C replacement (Rust, Nim, Zig, Elixir, Kotlin).
I don't know anything about Go, but I've spent a lot of time working in a lot of languages. Structural typing is a bad idea. There are a few, limited cases where it's necessary, e.g. we have it in TypeScript because TS ultimately has to work within the limitations of JS. But if you don't have those sorts of limitations, intentionally implementing structural typing in your language is borderline negligent.
Interfaces don't just define a bag of method signatures. Interfaces are semantic, too. An Employee, a Gun, and a ClayPot might all have a method named `fire()`, but they all mean different things. But with structural typing, we can load our Employees into a Kiln and give HR a Gun to downsize the company, and nothing will stop us.
The downside of nominal interfaces seems worse: you have to import the interface which can lead to awkward situations like cyclic dependencies and type-only packages (a la Haskell). In practice, the extra friction of nominal interfaces also seems to encourage wide interfaces in contrast to Go's narrow interfaces.
- Go’s lambdas are extremely verbose. “Concise but clear” is a big part of what people live about functional data structures, Go won’t have the concise part
- Go will still have almost no support for immutability, which works beautifully with functional data structures
Code like this is nice to write:
users.map((user) => user.id)
While code like this isn’t:
users.map(func (user User) string { return user.id })
There is a way to specify what file is actually targeting inside the file, through //go: "pragmas" too
[1] https://stackoverflow.com/questions/25161774/what-are-conven...
Now a bit more background, this goes back to C and C++, and is considered a best practice to name translation units as name_os_arch or similar pattern, with the header file being only name, instead of #ifdef spaghetti.
This is actually one of the few things I think Go did right.
...seriously wtf
It will catch someone out who uses BSD as an acronym for something in their domain model (Bulk Sales Discount?) then xyz_bsd.go doesn't compile.
If a file's name, after stripping the extension and a possible _test suffix, matches any of the following patterns:
*_GOOS
*_GOARCH
\*_GOOS_GOARCH
(example: source_windows_amd64.go) where GOOS and GOARCH represent any known operating system and architecture values respectively, then the file is considered to have an implicit build constraint requiring those terms (in addition to any explicit constraints in the file).But also, I don't think it's specifically hate, it's more of a reaction to the overwhelming wave of posts here (and basically on every programming forum) from around 2014 to 2018.
Go was so hyped it was unavoidable that if you were starting a project, dozens of comments would be shouting at you to use go.
Some posts are people finally getting to say, "I told you so, but I was against the crowd a few years ago" Some posts are people saying, "Go doesn't really fit this use case" Some posts are people just academically sharing the language features you don't get when you choose go.
Basically, in my opinion (and as a developer of a large go codebase that I really love), thousands of people hyped up go as a silver bullet or a "near-perfect" language. This is obviously not true, go has many downsides. When you tried to bring them up before, you were downvoted and pushed aside for the hype. Now that people are maintaining legacy go code, there's more appetite for these conversations about go's tradeoffs.
I especially find it funny that people assume that people like Rob Pike and Ken Thompson who have published research papers on computer science forgot or misunderstand modern language features. They purposely made their language this way, for better or for worse. They never stated that they were trying to make the next Java. And all of the successful projects that have been released so far written in Go is proof enough that it seems they know what they're doing.
People complain online for a language they won't use instead of using the languages that are "superior" to make useful modern software.
Now there's a lot of Go code in the wild, projects people want to use or extend and programmers who will choose it by default, and it's starting to displace C#, Java, and C++ where the comparisons become a lot more preferential and vague. Do you want a faster GC or deterministic allocation? Nominal or structural interfaces? People fear change, especially in a field where change is only loosely correlated with improvement.
val foo = if (bar) "this" else "that"
and you don't need two separate assignments.They removed it because they made the regular "if" statement into an expression, so the separate "ternary" operator expression is superfluous. Rust et al made the "if" situation better than C; Go made it worse.
Rust, Nim, Zig, and Go are all fair game in that list, as far as I know being a C replacement is in the mission statement for the creation of all those languages.
https://ziglang.org/documentation/master/#if
Also, Zig is so into expressions over statements that they plan to remove “function definition as statement”, which I think is awesome. In other words, conceptually function definition is a compile-time expression that creates the function.
Edit: here’s that Zig rfc:
foo = if x, do: 1, else: 2
I think Go is quietly taking over some business domain applications, reminiscent of Python ca. 2003 a lot of companies I see are using it for a few key components that benefit heavily from memory savings of value types and/or easier naive concurrency, but they're relatively quiet about it. There's also a small but significant cohort of junior devs today who learned to program by making games on fantasy consoles or homebrew hardware projects, want to keep working with those data-oriented patterns they learned C and C++, and Go supports that style a lot more idiomatically than Java. As much as I sometimes rant about "kids today", more of the current generation know the true value of a MB or a ms better than that of 10 years ago (Rust is helping a lot here, too).
2006-01-02T15:04:05Z-0700
It's unfortunate that the year is 2006 and sandwiched between the minute and TZ offset, but this keeps the day at Monday (1st day of the week, for many anyway) so that's nice.(Not to a level something like Typescript has it, but I'll argue that's a win. Typescript inferred typing can be hard to follow.)
Love Syncthing, didn't know it was written in Go. Though I wonder why they wrote MacOS client in Objective-C instead?
But the philosophy I always had was that as long as there were enough people like you paying for software, I wouldn't have to.
Friend of mine pirated photoshop when he was studying and after he finished and started his own business he started paying for it.
It's not perfect but better than your typical closed source software.
Everytime I have to use VSC to develop typescript and angular, I am having problems with finding definitions (works 30% of the time), code search (it takes longer due to the constricted interface), git operations (want to do more than a simple pull and push? good luck), and much more. WebStorm on the other hand has a lot less bugs, a more flexible interface, and more features. I am glad that people make an effort to make an IDE instead of an editor with IDE-style features and I'll gladly pay a very small amount of my salary to them.
Every workshop has higher costs than a software developer. Imagine a car mechanic propping up a car with 2 by 4 because they use what's available for free. No, they buy their $30,000 lift because they need it to get their work done quicker.
This is a far from convincing argument. Jetbrains IDEs are not the equivalent of a professional lift and the competing (often free) products are not the equivalent of a 2x4.
Is Jetbrains good? Well, I've used it for Java and was pretty impressed.
Is it $199/year[1] better than the free stuff? Well many people don't think so. It's fine if you only every use a single stack, but most of us use multiple languages and multiple stacks, now you're looking at $649/year (see link below) for all tools. Considering that my current personal development computer cost less than that years ago, is it now wonder that the price is considered too much?
I think the problem is that developers are looking at the Jetbrains products and comparing it to the value they get from other development purchases.
Compare:
A single $1k computer will last for many years, do every single development task needed to make money, be used for entertainment, and write all the actual software that will be sold. When it is too slow for dev (in a decade from now), it'll be repurposed for something else.
A single annual payment of $649 to JB results in a tiny increase in dev speed, which will disappear at the end of the year anyway. It won't make the code more robust, it won't help solve business problems any faster, it will only make code navigation faster.
For a dev, look what $1000 buys, and then look at JB for $650, and it doesn't look like all that good value for money anymore.
[1] the cost for Goland at https://www.jetbrains.com/go/buy/#commercial
The individual-license pack for all of their IDEs would set you back $250 as opposed to $650 commercial license.
I used to use "IDEA Ultimate", which is 30% cheaper than the All-Pack and supports installation of most of the other language plugins, allowing me to use a single IDE for everything. Nowadays I'm using separate IDEs as that seems to work faster.
I personally find the price worth it, as even a simple "expand selection scope" operation which I use many times per day just doesn't feel right in VS Code.
I stand by my 2 by 4 comment because they get the work done, just slower and more awkward. It's always super amusing how software developers with one of the highest salaries around the world (yes, even in poorer regions) complain about costs when other jobs require tens of thousands in initial investment.
Currently that shows a cost of 250 euros per year, or about 20 euros per month (excluding VAT which varies).
For most developers, that is indeed a relatively small amount (even I opted for the ultimate package, despite earning in the low 2 figures in Latvia), whereas the 650 euros for commercial licenses would be doable for any organization that cares about their developers' experience.
All of that is excluding their loyalty discounts, programs for students and non-profits, startups etc.: https://www.jetbrains.com/go/buy/#discounts?billing=yearly
Personally, whenever I see commercial software or a SaaS/PaaS/IaaS solution, I'm tempted to throw a brick through someone's window (figuratively) because those are likely to result in unreasonable amounts of vendor lock (especially with cloud services around Kubernetes management), but personally I haven't found a better IDE than what JetBrains offer.
For Java, all of the alternatives are worse: Eclipse is buggy and crashes (though some swear by its incremental compiler and integrations), NetBeans is kind of dated and struggles with projects that have 4000+ source files (though it's cool that Apache keeps it alive and there's the whole module enable/disable functionality and their VisualVM integration is great).
For .NET, Rider is easily up there with Visual Studio, even when you're doing something more niche, like working with the Unity game engine (the performance hints are nice), or just working on .NET apps.
For PHP, Ruby, Go, Python and other languages their tools feel competent and oftentimes suggest you whatever it is that you might want to do, be it setting up your runtimes properly, your dependency management systems, install all of the dependencies, import the project config/launch profiles etc.
For Node/JavaScript I have never found a good IDE, but maybe that's because the language is sometimes a mess to work with - e.g. getting only some very basic completion in some garbage 3000 line AngularJS controller because even the IDE has no idea what the hell is going on there, or having Vue 3 use the <script> tag for adding code imports, instead of detecting that i'd like to use <script setup> but then again, they're pretty speedy with updates and if you don't do anything too crazy with projects, then it should be good.
I don't have much experience with their C/C++ offerings, or their lightweight text editor (Fleet) or the likes of DataSpell, though their DB management offering, DataGrip is pretty okay too! Though you can also configure the individual IDEs like IntelliJ to show up hints for most decent frameworks.
If we refuse to buy products then we end up with companies offering just services with vendor lock in. Well...we already sort of ended up in such world.
foo := func() string {
if bar { return "this" }
return "that"
}() foo := "that"
if bar {
foo = "that"
}
Unless the assignment of "foo" is expensive, then you'd assign it in an else.If you really want to, you can do it in a single line too:
foo := map[bool]string{true: "this", false: "that"}[bar]> and you don't need two separate assignments.
Although I do use it far more often nowadays than a decade ago. I've even mentioned it before https://news.ycombinator.com/item?id=30384835.
On the flip side when you deploy it, it will like just work and run smoothly. Also very easy to add full integration tests as you can create a mock http server at runtime, paired with some db tools (go migrate) it’s very easy spin up a whole env and test it. It’s also very easy to get at lower level things like headers and cookies without it getting in the way. Also much of the web middleware is interchangeable or a few lines away from an adapter as there is a common interface in the std lib.
That is true, but also in some places that is also the status quo: especially in countries where the developers don't get 6 figure salaries and don't create as much value to their respective companies.
if they can't justify paying ~$100 for my ide of choice, then they are costing them selves far more in lost productivity, and that's not my problem.
Not saying Company should not pay that, but it's not $100
#[derive(Hash)] struct X{ ... }
Seems easy enough to me? The only annoyance is if a third party type didn't implement Hash, but you can solve that with a manual implementation instead of a derive.
How? I thought the orphan rule said you can only define trait implantations at struct definition or trait definition
In crystal there is a reasonable data-based default hash implementation that you can just rely on.
What, apart from string reverse, do you miss for real projects?
First, it has a lot of useless packages you typically wouldn't use, like "log" and "flag" (which work, but are way worse than third party alternatives like logrus and pflag), but also like "syscall" (as it says 'deprecated, use 'golang.org/x/sys' instead), "image/draw" (nope, you wanted 'golang.org/x/image/draw' usually), "path" for working with paths (you wanted "filepath"), "net/rpc" and "rpc/jsonrpc", "plugin" (almost always a bad idea), a chunk of "strings" (use "golang.org/x/text" for proper unicode support) and so on. Some of those are marked deprecated, most of them are not, and are just waiting for someone to accidentally use them.
That's issues I have with the stdlib and not stuff I'm missing though... Though I guess I really do miss a good logging library, or at least interface for external packages to implement so I can plug in logging libraries without rewriting the world.
One thing I do find missing frequently is a reasonable set type with the ability to do things like basic set operations (intersect, diff, etc). I constantly have to write ad-hoc for loops in go to do set operations, and it's verbose, non-obvious what the code does, and easy to get wrong.
But honestly, the main thing I'm missing isn't actually a package, but more about error handling for the stdlib as a whole, which is more a language issue. I really wish I could know what possible errors stdlib functions returned without, fairly often, having to read huge chunks of stdlib code to determine that.
Perhaps 40% of the stdlib documents the error type it returns in a message (like 'os.Chdir' always returns '*os.PathError'), but for the rest, good luck. Want to figure out what errors you might have to check for 'tar.Writer.Close()'? Well, the docs says "returns an error", the interface is "error", you have to read hundreds of lines of code to figure out the possible concrete types it could be. Maybe 15% of the time, you end up having to string-match on error messages because the error var or type is unexported.
- Do you want to reverse bytes, or codepoints?
- Do you want to reveres codepoints, or grapheme clusters?
- Do you really want to reverse grapheme clusters, or do you want to reverse some grapheme clusters while leaving e.g. sequences of control characters in the same order?
- Do you really want to reify any of this rather than iterate backwards in the existing memory?
C++: reverse(str.begin(), str.end());
Dart: str.split('').reversed.join();
Java: new StringBuilder().append(str).reverse().toString();
JavaScript: str.split('').reverse().join('');
PHP: strrev($str)
Python: ".join(reversed(str))
Rust: str.chars().rev().collect()
As I wanted the real deal and not a bunch of no name floppies with copied manuals.
That would be around 150 euros, without taking into account the inflation to modern days.
And to place the price in perspective, it was a third of the minimum wage, while the overall cost of my PC took 5 years for my parents to pay back to the bank.
If a language requires some IDE to make it usable, then I put it in the same camp as Java: Hope the competition are using it.
Because as messy as Java is, refactoring codebases in it that have been kept alive for close to a decade is surprisingly not madness-inducing (most of the time), at least in some of the sane frameworks. Apart from, you know, legacy projects basically killing your career in the long term.
I'm not sure what other language I'd feel comfortable with changing how some method works across 50 other places that call it and have the IDE do most of the heavy lifting.
Yes, I have Stockholm syndrome, probably. Yes, I'd prefer to retire to planting potatoes in a farm, rather than work with NullPointerExceptions.
Programmers are toolmakers, and are therefore harsh critics of tools they use; just like a carpenter will tell you everything that's wrong with the design, and choice of wood that went into a pricey, but ultimately-affordable-to-a-carpenter bench top. Having access to cheaper, good-enough alternatives is part of it.
I'm not writing my own IDE or my own database. I can, and I have, in times past. I do pay for tools that I need.
Arguing we should also buy every Jetbrains product is like arguing carpenters should buy line drawing AR goggles. Perhaps some will see the value in it, but it's far from an noncontroversial POV. If you feel a tool as as much downsides than upsides in your workflow, you don't use it, whatever its price is.
Yeah ok.
If you want your employees to be productive, you provide them tools to achieve that, otherwise you get what you pay for.
It's the Companies that are acting entitled in this situation, not the workers.
2. Physical tools vs making copies of some bytes. No need to retread this here, but bottom line: Not comparable.
(What do I mean by wildly different things?
C++: Swaps the string's contents in-place, and probably breaks any multi-byte code units unless you've got a parameterized std::string at hand.
Dart: Makes a new string but has to round-trip via an array, because... it doesn't have a string reverse? This seems like a really bad argument for your side!
Java: Reverses codepoints, but the fact you have to round-trip through a StringBuilder to handle this is also telling.
JavaScript: Same comments as Dart, but I believe this is broken, it will reverse surrogate pairs incorrectly.
PHP: Good luck figuring out what this does depending on your platform, locale, and moon phase.
Python: Another codepoint reverse, again not via strings but a lazy sequence, and also not even idiomatic - use `str[::-1]`.
Rust: And finally again... not a string reverse.
You want a Go slice reverse? You can get a perfect one post-generics.)
This is where the analogy breaks down, but they'd likely download a free one made by a consortium of other carpenters, which can be customized to their needs
> I do pay for tools that I need.
As have I: I was paying JetBrains yearly until they published plans to brick my IDE if I dared stopped sending them money. They walked this back after an uproar - but that episode showed me that I was also playing in their sandbox and subject to their every whim. I now default to using tools that can be forked at a moments notice (by myself or others)
Also, JetBrains IDEs were far ahead of the competition back then. For the tech stack and codebases I now work on (or perhaps additional experience?), none of the JetBrains IDEs are worth the effort. vim and a handful of plugins & scripts are adequate 95% of the times, VSCode takes me up to 98%, and it's diminishing returns beyond that
Well, it's structural, so you don't need other packages to implement an interface rather you need them to accept an interface. That also makes it clear it's a bigger ask - you're not asking a dependency "please also do X" but instead asking "please never need to more than Y".
> Want to figure out what errors you might have to check for 'tar.Writer.Close()'? Well, the docs says "returns an error", the interface is "error", you have to read hundreds of lines of code to figure out the possible concrete types it could be.
The concrete types it could be are unbounded, because `tar.Writer` wraps arbitrary `io.Writer`s. If you need multi-pathed error handling (usually people don't and are just making it out of habit!), worry about what things can do, not what they are.
When I'm looking at an error, it's typically for one of two reasons:
1. To set a correct status code, such as http 5xx (internal server error, our disk flaked) or a 4xx (user error, you gave us invalid input).
2. To provide a better error message, such as to localize it into an error string.
If you're building CLI tools for yourself, sure, every error is fatal and you can read english so you don't need either of those. For most go projects, both of those are relevant concerns for a large number of error paths.
Go's type-system does not help you at all.
Speaking of...
> Well, it's structural, so you don't need other packages to implement an interface rather you need them to accept an interface. That also makes it clear it's a bigger ask - you're not asking a dependency "please also do X" but instead asking "please never need to more than Y".
Yup. That is a big problem. That's the root of the error problem too, where every package returns the stdlib error interface, which is a tiny subset of what you usually want.
For this it's simple to wrap them at return site in something that offers `HTTPStatus() int` and check for implementing that interface, not any concrete types, in your handler.
Also, those error paths should be dangerously hard to mix in the first place, you shouldn't be letting invalid input anywhere near the disk to begin with.
Re. logging interfaces, I think you've missed the point. You want everyone to accept narrow interfaces so you can use the logger you want. You also want everyone to return wide error interfaces so you can categorize the entire universe of possible errors as you want. In the end this isn't a technical problem, it's an "I want everyone to cater for my use case" problem.
It's not though, the return site is inside the go stdlib. I cannot annotate it with new methods.
The only way to figure out how to translate all errors (whether to status codes or to other readable messages in localized languages) is to read the code and figure out what errors it might return.
> you want everyone to return wide error interfaces so you can categorize the entire universe of possible errors as you want. In the end this isn't a technical problem, it's an "I want everyone to cater for my use case" problem.
This is a technical problem. In Rust, libraries define error types and return "Result<T, MyErrorType>", which lets a library author decide what errors are interesting or not. If I think they have not classified an error that is useful for a caller, I can file an issue.
In java, exceptions have types, and I can know what types of checked exceptions a function might throw, and can similarly ask for more specific exceptions, or modify the library to provide them.
In go, _every_ library, due to go's error handling idioms and some mis-features of nils/type-inference, returns the most useless error type possible, the 'error' type, and I have to constantly read docs or code to figure out what types it might be.
I don't see how this isn't a technical issue with the language that, at the type-system level, it makes it an anti-pattern to return concretely typed errors in a way the type system can recognize them.
I don't agree with how you're characterizing what I'm saying as being "catering to my use-case".
Do you just never actually need to classify an error? Is it somehow weird to want to be able to provide a localized error to a user? Doesn't everyone have these problems too?
type httpClientError struct { error }
func (err httpClientError) HTTPStatus() int { return 400 }
func (err httpClientError) Unwrap() error { return err.error } // if needed
It's not even anything special around `error`, Go's entire type scaffolding is built around doing stuff like this.> Do you just never actually need to classify an error?
Infrequently, and virtually never for errors types I didn't write myself (other than a tiny number of sentinels like UnexpectedEOF or DeadlineExceeded).
> Is it somehow weird to want to be able to provide a localized error to a user?
Yes, it's unusual for error details (rather than e.g. outcomes) to be localized for display directly to non-technical end users. This is also true of exception messages in Java. General-propose desktop client software is rarely written in either language.
I think you're too focused on the specific issue to see my general point about interface size.
Even within your own program, you now have to read the code in "httputil" to understand what possible error types can be returned.
It's idiomatic to never return concrete error types, whether from the stdlib, or third party libraries, or even methods within your own program.
Even for types you do write yourself, you still have to either memorize what errors each method may return, or you have to constantly refer to docs or source code reading.
Clearly you think this is fine and go's type system is good enough for your use-cases, but every larger go program I've worked with, error handling has been painful since the errors are effectively untyped.
I assume we must have worked on different types of go projects if you haven't run into pain with this.
> Yes, it's unusual for error details (rather than e.g. outcomes) to be localized
I absolutely agree that it's outcomes which are localized, but to determine _outcomes_, you have to classify errors. If the _outcome_ is "File doesn't exist", that's a different error than "permission denied", so you need to classify. But the type you have is "error", so you have to constantly refer to docs.
Xerox PARC already showed how.
Nonetheless, Go does provide ways to check whether an error either is or can do what you want, and ways to annotate errors with logic specific to your program. An `httpClientError` is an `error`. When you get an error from a source you want to treat as a 400, you wrap it and return it, as an `error`. You use `errors.As` on it as a concrete type, or an `interface { HTTPStatus() int }`, to use the method you've added.
Regarding localization, which is a significantly different problem - the outcome is e.g. "file can't be opened". It's hard to write good error messages based on the language's error messages but this is not a Go-specific problem at all. Either you constrain your operations to the point you can bound all your error types, or you don't and report the outcome + raw message instead of trying to localize causes. And yes, this is an unusual space to be using Go or Java.
Since you mentioned Rust, we could also consider how it solves the problem - `Write` returns a `Result<usize, io::Error>` - `io::Error` has a (almost uselessly long and yet still) non-exhaustive `ErrorKind` - the last of which is `Other`, "used to construct your own Errors that do not match any ErrorKind." I.e. even in Rust's type system, they punted because otherwise you can't easily compose anything.
Whatever makes them happy and productive, that's great. I've seen people use Vim and its crazy how productive some people are with it.
My point is complaining about the price of some software like it's blocking them from doing anything. They spend all this money on all this hardware and software, but when it comes to development, oh it costs too much I don't want to pay $90 for something to earn money...
This feels pretty weird to be faulted for using other companies' products. And no, switching to Jetbrains' doesn't make me more money. Could be the reverse from my past trials.
↑ that was in my answer (with the typo, on the “as as” instead of “has as”, my bad)
You might be confounding mine with another comment. I recognize the talent and expertise of JetBrain’s staff, but don’t like their products in general, and use VSCode as a primary editor, and (paid) Textmate for the rest.
To your general point, looking at project like Bitwarden, with their initial kickstarter and their current revenue, I don’t feel like people are restraining from paying for useful software, even when it has a generous free tier.
I ran JetBrains software on one of those 1.5ghz MacBook 12” and it was totally fine.
I don’t think it needs “powerful” hardware.
Not like visual studio. Now that’s a pain!
That depends. If you have multiple large projects open at the same time, it'll eat a lot of RAM, even if it won't be too CPU intensive.
I run it on a ThinkPad that has 32 GB of RAM, when I have about 6-7 instances of the IDE open and all of these services running locally (generally Java projects, the largest of which is around 4000+ source files), then it gets close to the resource limits.
Switching between Visual Studio is 15-20 seconds of wait time while it decides if any files have changed.
I don't duplicate anything, my CI/CD pipelines consume MSBuild, Ant, Gradle, Maven, CMake, XCode, package.json, gulp, webpack files just as easy as my IDEs.