This is your big hangup with Go? You added a new field, didn’t use that field anywhere, and the compiler didn’t complain? I’ve heard a lot of valid criticisms of the language but this is a new one for me.
type Sample struct {
A int
B int
}
can be initialized as Sample{A: 1, B: 2}, or Sample{1, 2}.The name-based method allows arbitrary elision of fields, which will be initialized to their zero value. This includes Sample{}, which will be interpreted as a name-based initialization that specified no fields.
The position-based method requires all fields to be specified, in order, with the correct type, or it's a compile-time fail.
So if the author of the article could have gotten the latter behavior with a slight syntax tweak.
This would not necessarily entirely satisfy, though, since you can't do any mix-and-match, and in particular, it can be nice to have names on the larger structs even if you want them to be fully initialized, but there is no in-between. But, nevertheless, if you are willing to pay for the behavior that the compiler will complain on any type-changes to the struct, including growing or shrinking, Go has that.
It seems to be the Go community's "best practice", above my objections, to claim that all struct initializations MUST use the name-based initialization and that positional-based inits is a mistake, on the theory that if structs change and add fields it's important for all uses of structs to continue compiling without changes. If the author's introduction to Go came from a tutorial from someone who believed that, they could easily have picked that up mistakenly as a characteristic of the language itself.
My objection is that there's a time and a place for each behavior, and there have been plenty of times I've been grateful for the compiler pointing out every place I need to change my struct to include a new member because I could tell it was a "tuple-like" struct that should always be initialized with all the fields. You can argue with the putative "best practice" or agree with it, but fortunately it doesn't really matter because you can do the one you want regardless of what the community thinks. There is no chance either method is ever going to be removed.
1) It's much more difficult to read since you have to know the position of elements of the struct
2) You swap one compile time error for another: in the case you add and remove a field of the same type, it's entirely possible the program will compile fine if you use the positional initialization instead of the named one.
#2 makes me think that the author would still be unsatisfied
Edit: spelling
One other reasonable alternative for OP in Go might be to simply use initialization functions (NewSample() or sample.InitWithX()) for certain structs. It's a bit more flexible with the ability to have multiple initializers and non-zero default field values (and of course also solves the issue of generating compile errors in the rest of the code when new fields/parameters are added).
1. Go doesn't follow "you pay for what you use" model.
2. Go isn't too deeply invested in compilation as means for catching errors.
Above are not binary language decisions. If you go in one direction strongly then there are consequences in other. Therefore all languages chose some balance point (aka compromises). Go has chosen a balance point that is weaker than Rust (and may be even C++) but stronger than Python.
Except when you've imported a package you're not currently using, that is a mortal sin :)
Keep in mind that dataclass defaults suffer from the same "mutable default arguments" issue as function parameters.
To be fair, isn't that implied since Go uses a GC? The "pay for what you use" model is only really viable for languages that don't rely on a fixed GC runtime. (You could have "pluggable" GC libraries like the Boehm collector for C/C++, but those imply very different tradeoffs.)
It's not "you" in the traditional formulation. It's "CPU." You, the programmers, often have to pay continuously for "you pay for what you use."
Hell, just the other day I complained to my backend team that the PubSub event was passing null for a non-optional array, and it was traced to the Go code declaring a map variable without ever initializing it. The compiler never caught that, it wasn't until it hit my Swift code that it became apparent.
We've had bugs in prod because of this "feature". It's another bad design decision in golang.
There are APIs with autogenerated structs that span a lot of fields (even thousands)... Imagine having to initialize each field when you just want... the default zero value...
Enforcing the initialization of all fields achieves relatively little given that public fields could easily be modified elsewhere. Requiring initialization to anything more specific than an automatic zero value is only useful in languages that have read-only fields.
So the only way to handle this in Go is to make struct fields private or accept that all combinations of public field values are valid (including the zero value) and deal with it at the point of use (or rather at all points of use).
I'm trying elixir currently, and it makes me want to rip my hair out. No for loops, no typed parameters, implied returns. Who in the world though any of this was a good idea? IO.puts() refuses to print certain objects (list of ints) and just prints a newline, making it useless for debugging.
I'm very very excited to try Rust, and I'm glad I've been able to choose the languages I work with up to this point in my career.
> No for loops
But we do have for loops? That's what comprehensions are: https://elixir-lang.org/getting-started/comprehensions.html
> no typed parameters
But if you need types on your parameters, you have Guards, which... check for type? Sure, you don't type every last parameter, but if you want that you can upgrade to Dializer and get that feature too. It's just not as useful as you likely treat it.
> implied returns
I understand that some may not like this, but it comes out of the idea that generally, you want to avoid "side effects"- ie, things in code that happen without anyone but the original function causing them being able to tell. Elixir doesn't completely lack them, as that would be pretty unreasonable, but it does at least ask that you're clear about the purpose of functions by having them always return something.
> IO.puts() refuses to print certain objects (list of ints) and just prints a newline, making it useless for debugging.
This seems like something must be going wrong. `IO.puts` certainly prints lists of ints, however, it might also be mistaking them for a charlist, which is definitely a concern. That's why generally, you want to use `puts` for text, and `IO.inspect` for data structures. As an added convenience, if you want to annotate your data structure with text, you can modify `IO.puts` like so: `IO.puts "List: #{inspect(list_of_numbers)}"`. That will parse the list to the inspect version and insert it into your `puts` line.
For the record, I'm not saying any of these necessarily shouldn't be a reason to not like Elixir- we all have our preferences, but the reason behind many of these choices is philosophical, and I'd be happy to explain anything else that has puzzled you, if you're still interested in learning more about Elixir.
Agree about typed parameters + implied returns, but you can get some of that back using guards + dialixer/dialyzer.
Fair warning, though, before Go, I wrote Delphi for a living.
Also, try `IO.puts inspect foo` for printing stuff.
`IO.puts` can be compared to print() behavior in other languages expecting toString() is fully implemented (`to_string` in this case for your custom typed data structs. [2][3])
[1] https://hexdocs.pm/elixir/Enum.html
[2] https://hexdocs.pm/elixir/String.Chars.html
[3] https://code.tutsplus.com/articles/polymorphism-with-protoco...
I think nim is the best thing out there for building tiny CLI utilities. It’s a fun language to code in.
[0] https://docs.rs/reqwest/0.9.18/reqwest/
[1] https://docs.rs/hyper/0.12.29/hyper/client/index.html
Maybe I have too much desire for instant gratification, but it sucked the fun out of the project.
Django/Flask are great tools for spinning up a CRUD application with authentication/authorization, API endpoints, and a template language if I don't want to build out a React or Vue SPA.
Rust is a great language that's opened me to new domains of programming, and also gives me the ability to write more performant, hardened API endpoints at the expense of productivity in hours (this gap will definitely shrink as I get better and as rust frameworks mature, but I'd be surprised if it went away entirely)
If a developer views a suite of tools they know very well as "something they used to use", they're more likely to make future architectural decisions based on incorrectly viewing framework-powered python as a relic of the past, rather than a viable (and often preferred, if hours of work are factored in) alternative.
However I use Python very often still, just not for every task. Just like you said: Rust is another tool in the tool box and it isn’t that single purpose machine that you buy and use once for a single project, it is that japanese handsaw that is incredible useful and powerful in the hands of the right person. But sometimes you just need to make many cuts fast and then the handsaw (no pun intended) doesn’t cut it.
Python certainly has it’s place for me, but so does have Rust..
all that said, when you have to do advanced things and go unsafe, it can feel really annoying (fighting the compiler) and limiting. most common problems can be modeled in rust without issue, but if you have a clear idea of something that you want to do that's more advanced... the language will actively work against you, that's its goal. that has both a good and a bad side. maybe it will get better in the future. still an awesome language today.
No! One shouldn't mindlessly rely on the compiler "to get your back." Instead, one should code (or alter code) so that you've manipulated the situation, such that the compiler will flag any errors. With a language like golang, this means that you should make sure that the "zero values" are always copacetic. (Analogous to crash-only software.)