500GB isn't a lot of data, and $3K/month seems like an extortion for that little data.
Having said that, MongoDB pricing page promises 99.995% uptime, which is outstanding, and would probably be hard to beat that doing it oneself, even after adding redundancy. But maybe you don't need that much uptime for your particular use case.
Yep, we just migrated to Atlas, and the disk size limitation of the lower instance tiers pushed us to do a round of data cleaning before the migration.
Also, we noticed that after migration, the databases that were occupying ~600GB of disk in our (very old) on premise deployment, were around 1TB big on Atlas. After talking with support for a while we found that they were using Snappy compression with a relatively low compression level and we couldn't change that by ourselves. After requesting it through support, we changed to zstd compression, rebuilt all the storage, and a day or two later our storage was under 500GB.
And backup pricing is super opaque. It doesn't show concrete pricing on the docs, just ranges. And depending on the cloud you deployed, snapshots are priced differently so you can't just multiply you storage by the number of the snapshots, and they aren't transparent about the real size of the snapshots.
That works for up and down. But what about side to side? I don't know anyone who inverts left/right. If the lever was at the back of the head, won't left/right be inverted too?
Put your finger on top of the character's head. Left and right are still left and right, but forward (up on the controls because of how they're positioned) makes the character look down and backward makes the character look up.
It's really easy to improve the performance of Go implementation as compared to python or Java. There are lots of built-in tools to help you (like the profiler), and the resulting code is really very legible even to fresh college grads.
This is based on my first hand experience, but YMMV.
For a language designed for fresh-out-of-college engineer to pick up in a few weeks and be effective, it is very easy to squeeze out a lot of performance.
* built-in profiler.
* built-in escape analysis tool.
* It's easy to pass pointers instead of copying data.
* []byte is sub-slice-able, with a backing array. This does throw people off occasionally, but the trade off is performance.
* Go lets you have real arrays of structs, optimizing cpu caches.
* Built-in memory pools
And more.
And if you look at "non-idiomatic" performance code, they are surprisingly legible by the said fresh engineer. It's as if the designers didn't want to give up all the usual C performance tricks while making a Java/Python kind of friendly language, and this shows.
Of course Go can go only so far, due to the built-in runtime and GC. But it gets very far. Much farther than at first glance, or second glances that language snobs would give credit for.
Rubbing alcohol is isopropanol. Drinking alcohol is ethanol.
You can get “industrial ethanol” that has some methanol added to prevent people from consuming it, and pay liquor tax on it. These are for chemists for specific applications.
Contrary to popular beliefs, anarchists have no problem with constitutions or laws or governments. Anarchy is ANti-hierARCHY, which in the extreme case extends to not accepting the hierarchy of the State (i.e. an organization with a license to exert force over all others in society). But,
1. Many anarchist movements do not demand this much change. They only demand removal of specific forms of hierarchies they think most problematic. The Occupy movement for instance was demanding the curtailment of the political power of the 1% over the 99%. Its always better to think of political movements as directions of evolution in political space, rather than specific destinations.
2. Then how do anarchists propose that laws/constitutions be imposed? By consensus and discussion. By making sure everyone is on board. Or by temporarily giving someone conflict resolving power (as in the Debian case). Plenty of societies and organizations operate this way, and work fine. Read The Dawn of Everything for some historical examples. See a region in Syria [1] as a modern example.
The math of health providers is always super sketchy, because they 'donate' their emergency service to the indigent at a non-discounted rate and deduct that from their profits.
Aside from the fact that those non-discounted rates are astronomically high in the USA, they also don't have to worry about anyone analyzing the list of services provided, so the medical billing errors in the provider's favor don't have a chance of being questioned.
Large medical provider accounting is right next to Hollywood accounting in the staggering games played with profit, expense, and loss.
> The math of health providers is always super sketchy, because they 'donate' their emergency service to the indigent at a non-discounted rate and deduct that from their profits.
This makes no sense. If they are the healthcare provider, then services provided to the indigent go in the expenses column, and revenue is in the revenue column.
And you cannot “deduct” anything from profit, it is literally all revenues minus all expenses. And also Kaiser is a non profit.
> If the workers get paid more through this strike, it will translates directly to higher prices.
Has Kaiser not raised prices for health insurance premiums or other fees since 2020? Because that would be notable and they absolutely should lead with that.
My suspicion is they didn't mention that because they have been raising the fees they charge their customers faster than they have been raising wages, like many employers.
Either way, it doesn't matter all that much if their wages aren't keeping pace with inflation. That would almost definitionally lead to the kind of labor shortages described in the article.
I've had the same Medicare advantage plan with Kaiser in Colorado since 2019: My monthly costs for Kaiser, and then for Medicare part B:
2019: 370.50 135.50
2020: 360.50 144.60
2021: 294.50 148.50
2022: 236.50 170.10
2023: 236.50 164.90
2024: 236.50 ??
Before 2019 I had a different plan. I was retired but not yet eligible for medicare. Prices for such pre-medicare plans have increased significantly over this period.
edit fix formatting
The way I read their deletion terms, if you have some money in the account's Play Store Balancee (say by depositing a $10 gift card), they won't delete those accounts.
The same is apparently not true about the old Grand Central phone numbers though. The account will still be active for phone but the number will be gone.
> If a programming language supports union types and type inference then there is no need for such a "special" language feature
Laughs in Go.
> While Zig's errorhandling is not bad and miles above Go's
Laughs again in Go. Go has no "foundations" regarding errors, it's just a convention. It has no union types. It doesn't have weird corner cases. It's just a returned value you can handle. Or not.
Of all the error handling paradigms I've seen, Go's requires the least amount of "specialized thinking" (try/catch or whatnot)--it's just becomes another variable.
Go's lack of sum types mean that there is no static check for whether the error has actually been handled or not. Go's designers went to all the trouble of having a static type system, but then failed to properly reap the benefits.
Sum types are the mathematical dual of product types. It makes sense for any modern language to include them.
Ever since I learned of sum types, they have ruined my enjoyment of programming languages which don't have them. I sorely miss them in C++ for example (and std::variant is not a worthy alternative).
I don't understand why any new language wouldn't have them.
Pedantic typechecking is like learning to spot improper kerning, you think it’s a good thing but you spend your entire life cringing at the world around you.
std::variant is a good example of many things bad with c++ improvement process, as a language.
If you want to just pattern match on type of visitor there is “another convenience helper” that you need to bring, and result still looks not pleasant.
Introduced in like c++17, even in c++23 you still need to write a std::visit to process it. Committee members waste time on yak shaving that std::print
In theory the lack of sum types sounds like a drawback for Go error handling, in practice it does not matter at all IMO.
So far I have never worked a Go project without a strict linter enabled on the pipeline checking that you handled the case when err != nil. I don't care if it is the compiler or the linter doing it, the end result in practice is that there actually is no chance of forgetting to check the error, and works just as well as a stronger type system while also making the code stupidly obvious to read.
> no chance of forgetting to check the error, and works just as well as a stronger type system
A linter-based syntactic check is no substitute for a proper type system. A type system gives a machine checked proof. A heuristic catches some but not all failures to handle errors, it will also give false positives.
Error handling via sum types only enforces the rather weak constraint that you cannot access a non-error return value in the case where the function returns an error. It certainly doesn’t catch all failures to handle errors. For example, in Rust you are perfectly free to call a function which returns a Result and then ignore its return value (hence ignoring the case where an error occurred). Go’s error checking linters impose stricter constraints in some respects than the constraints on error handling imposed by Rust’s type system.
> only enforces the rather weak constraint that you cannot access a non-error return value in the case where the function returns an error.
This "rather weak constraint" as you put it, completely solves Tony Hoare's "billion dollar mistake": null pointer exceptions. Something Go also suffers from due to lack of Sum types. With regard to your Rust example, the compiler will give a warning that can be turned into an error to completely prevent this, if desired.
As the parent said, sum types are "foundational" and have many applications for writing safe statically checked code. Eradicating null pointers and enabling chainable result types are only the tip of the iceberg.
> This "rather weak constraint" as you put it, completely solves Tony Hoare's "billion dollar mistake": null pointer exceptions.
Yes. However, it doesn’t do what you seemed to be suggesting that it does, which is “catch all failures to handle errors”. You correctly note that Go’s linters can’t always do this, but also seem to erroneously suggest that Rust’s type system somehow can. This is backwards. Go’s error linters catch most instances of ignored error values, whereas Rust’s type system doesn’t do anything, in and of itself, to ensure that error values are not ignored. Of course there are compiler warnings to catch unused errors in Rust, but that’s fundamentally the same thing as the warnings you get from Go linters, and has nothing really to do with sum types. Whether or not an error is ‘ignored’ or ‘used’ in any interesting sense is not a formal property of a program that can be formally verified. (Yes, linear types, etc. etc., but you can formally use an error value in that sense while in practice ignoring the error.)
By the way, you don’t have to preach to me (or really anyone on HN) about the virtues of sum types. I’ve written a fair amount of Haskell and Rust code. My issue here is not with the utility of sum types, but with the erroneous claim that they somehow remove the need for linters or compiler warnings that flag unhandled errors.
> but also seem to erroneously suggest that Rust’s type system somehow can.
I didn't bring Rust into this discussion, it is hardly a model implementation of sum types and using them for proofs, but it is certainly a step in the right direction.
> My issue here is not with the utility of sum types, but with the erroneous claim that they somehow remove the need for linters or compiler warnings
You are misrepresenting my posts, I am responding to an erroneous claim that linters are a satisfactory substitute for sum types.
>> no chance of forgetting to check the error, and works just as well as a stronger type system
>A linter-based syntactic check is no substitute for a proper type system. A type system gives a machine checked proof. A heuristic catches some but not all failures to handle errors, it will also give false positives.
Thread B:
> Go's lack of sum types mean that there is no static check for whether the error has actually been handled or not.
>>I dunno, my IntelliJ calls out unhandled errors. I imagine go-vet does as well.
>>>A simple syntactic check will only ever work as a heuristic. Heuristics don't work for all cases and can be noisy. The point is, no modern language should need such hacks. This problem was completely solved in the 70s with sum types. [emphasis mine]
What you said in these two threads seemed to suggest that the functionality of Go error linters can be subsumed by a 'proper type system' that includes sum types, and that such a type system would catch all failures to handle errors. But sum types by themselves do nothing to force handling of errors. You could add sum types to Go and you'd still need a linter to perform the exact same error handling checks that Go linters currently perform. Whatever kind of value a fallible function returns, you can always ignore that value as far as the type checker is concerned (unless you add something like linear types to your type system, which are orthogonal to sum types).
Apologies for the confusion re Rust. For Rust just read 'a language that handles errors using sum types'.
> Sum types by themselves do nothing to force handling of errors,
If you have Haskell experience, then have you ever wondered how it is considered "null safe" and does not throw null pointer exceptions? Perhaps it is because optional "Maybe" types (the simplest form of error) must be explicitly unpacked? Yes, Haskell, being an old language without a sound type system, permits "fromJust" and its exceptions (a side effect) are not tracked like other effects. But despite this, are you seriously claiming that sum types "do nothing" to achieve this null safety?
If you want to understand the full proving power of sum types, I do not suggest Rust or Haskell as a model example. Coq, Agda, Idris or ATS will be better examples.
>But despite this, are you seriously claiming that sum types "do nothing" to achieve this null safety?
No. I said nothing about null safety. What I said is that “sum types by themselves do nothing to force handling of errors”. In fact I imagine that’s one of the reasons that Haskell uses exceptions for error handling in the IO monad. If Haskell had a non-raising function like
then you could of course attempt to open a file without checking for an error:
main :: IO ()
main = do
openFile “/foo/bar” ReadMode
putStrLn “Did the file open successfully? No idea”
Sum types (by themselves) cannot be used to prove that all errors have been handled. In fact, formal proofs of this property using other type system features (such as linear types) are of fairly limited practical value, given that merely 'using' an error value in some type-theoretic sense doesn't necessarily entail actually taking appropriate action to handle it.
> No. I said nothing about null safety. What I said is that “sum types by themselves do nothing to force handling of errors”.
Maybe (null) types are the simplest form of error type, with null pointer exceptions being the simplest from of unhandled error. They are therefore the easiest example to illustrate my point. You cannot simply choose to ignore them and remain credible. Haskell's broken old IO APIs are hardly a model example. Your Haskell code will at least give a compiler warning for ignoring the output. I would configure the compiler to turn this into an error.
>with null pointer exceptions being the simplest from of unhandled error.
I don't know of any practical language that forces you to handle the Nothing condition of a Maybe. Haskell has fromJust (as you note), Rust has unwrap. I suppose Idris could become practical one day, but it's not there yet. More fundamentally, without something like linear types, nothing in the type system can force you to check that a value of a particular sum type instantiates a particular variant. You're always free to ignore values, which means that you're free to ignore error conditions.
> Haskell's broken old IO APIs are hardly a model example of anything
I don't quite see what you're getting at here. My example function isn't part of Haskell's IO API. It's an example of what Haskell's IO API might look like if it used sum types for error handling rather than throwing exceptions. I fail to see how there can be anything inherently 'broken' about a hypothetical function that opens a file and returns either a file handle or an error.
>Your Haskell code will at least give a compiler warning for ignoring the output. I would configure the compiler to turn this into an error.
So you're saying that you'd configure the compiler to do exactly the same checks that Go error linters do...none of which have anything to do with sum types.
> It's an example of what Haskell's IO API might look like if it used sum types for error handling rather than throwing exceptions.
Apologies I missed that bit, it is indeed a perfectly reasonable API.
> So you're saying that you'd configure the compiler to do exactly the same checks that Go error linters do...none of which have anything to do with sum types.
We are arguing semantics as to what constitutes a "handled error". If a user chooses to explicitly throw away the error and not use the value, then you are arguing it is not handled. I am arguing that it has been handled (and checked as such). Either way sum types are a step in the right direction, despite all the shortcomings and unsound type systems of "practical" languages.
Rust compiler warns about ignoring a result of functions annotated with #[must_use]. This is optional because if a function has no side effects, then ignoring its return value is not a problem and shouldn't be warned about.
Yes, but that’s just the sort of linting you can also get vía Go tools. It’s not something that’s possible because of sum types.
> if a function has no side effects
A property which of course is not tracked by Rust’s type system. My only point here is that neither sum types in general nor Rust’s specific implementation of them provide any means of ensuring that errors are handled. They do other nice things, just not that.
No language has a 'static check for whether the error has actually been handled or not'. In Rust, for example, you can just 'unwrap' an error. In Haskell you can use 'fromJust'. And in Go you can just ignore 'err' and assume it is 'nil'.
Sum types might be the 'mathematical dual of product types' but programming languages are not mathematics. The possibly implementations of sum types are quite varied. It makes sense in low-level languages for the programmer to use what makes sense in the particular situation.
Unwrap and fromJust can be disallowed if need be, they are "unsafe" convenience functions whose use can and should be tracked. Not all languages with sum types will permit them. Rust also has "unsafe" code blocks, should we also claim it is therefore not memory safe? Some would try to do so, but at least this unsafe code is tracked and not idiomatic.
> programming languages are not mathematics
This may be how you choose to view them. But many of us seeking to build safer and more correct software aim to make programming more like mathematics. Mathematics tells us how to compose and tells us how to prove. Both things the software industry is currently failing at.
>Unwrap and fromJust can be disallowed if need be, they are "unsafe" convenience functions whose use can and should be tracked.
And with the same sort of third-party tools you use to 'track' those and ensure they're not used, you can track unused error returns in Go or C.
> Not all languages with sum types will permit them.
All do.
>Rust also has "unsafe" code blocks, should we also claim it is therefore not memory safe? Some would try to do so, but at least this unsafe code is tracked and not idiomatic.
Absolutely we should! Rust fanatics try to claim it is a memory-safe language when it isn't. Real memory-safe languages like Java have existed for far longer.
>This may be how you choose to view them. But many of us seeking to build safer and more correct software aim to make programming more like mathematics. Mathematics tells us how to compose and tells us how to prove. Both things the software industry is currently failing at.
He still has a point. In theory you might need a language like Idris/Agda, but in praxis it still makes a big difference.
It is true that you will see that a function can return an error and that you choose to ignore it. It's also true that you can do the same in many other languages that use sumtypes.
But it is still different. Because while ignoring an error in go is as easy as putting an underscore next to the happy-case, in languages with sumtypes that doesn't work.
The equivalent in other languages would be to return a struct and then just access one value and ignore the other one. In that case, the practical implications would be the same.
But when using a sumtype, a few things change.
First, you can not just access the happy-case value, you are (or at least can be) forced to also "access" the unhappy-case value. Either in a pattern match, in a fold-function and so on.
You know have to return something, even if it is an empty value our "escaping" by throwing an error.
On top of that, what happens if a function can partially succeed? Take a graphql request as a practical example where this is quite common.
With Go's style of error handling, how do you model that? I.e. say you need to redactor a function that previously either succeeded or failed into one that now can partially succeed and fail.
In a language with sumtypes I would now switch from a sumtype Success|Error to more complex type Success|Error|PartialSuccess which makes it a breeze to refactor my code because the compiler will tell me all the places where I have to consider a new case and what it is.
I'm genuinely curious, how would you model that in Go and what implication would such a refactoring have on existing code?
You can always implement tagged unions in any language with untagged unions, so in a broad sense you can emulate sum types in situations where they make sense but use simpler code elsewhere. I might do that in C. Depends on the situation. It also obviously isn't a proper answer, I am sure you will agree, to just emulate the feature that I am saying is unnecessary. That works in Lisp where you can elegantly add language features with proper macros. In C, you cannot.
I would probably simply do in C the same thing as usual:
int
function1(int arg1, int arg2, int *out1, struct foo *out2)
{
if (part1(arg1, out1))
return 1;
if (part2(arg2, *out1, out2))
return 1;
return 0;
}
// Oh hmm, some callers can do something useful with a partial result.
// Assume the internals are more complex, because obviously in this simplified example you
// could just make them call part1 directly.
enum { SUCCESS, PARTIAL_SUCCESS, FAILURE }
function2(int arg1, int arg2, int *out1, struct foo *out2)
{
if (part1(arg1, out1))
return FAILURE;
if (part2(arg2, *out1, out2))
return PARTIAL_SUCCESS;
return SUCCESS;
}
This is compatible with old callers, even, who treat any nonzero result as failure and any zero result as complete success (the normal pattern in C).
Yes, the caller needs to check the result and avoid looking at out2 if you dont get SUCCESS and avoid looking at out1 if you get FAILURE. But this sort of thing is de rigeur in C. Your compiler (or a linter, and optional warning flags are essentially linters anyway) will warn you if you ignore the result and if you switch on the result will warn you if you ignore a case.
But obviously it is left up to you to avoid the "dont touch X if Y" stuff. Eh, that is in my experience not the hard bit of writing C. The hard bit is anything involving dynamic lifetimes or shared mutable state. The nice thing is that you can avoid this in C! Most people don't. The easy path is calling malloc everywhere and getting yourself into a muddle. The simple path, which is better in the long run, is to use values and sequential, imperative code. And if you do that, you realise that C's design makes way more sense. That is how it was designed to be used. Dynamic lifetimes of objects? It is like trying to use Rust to represent linked lists. People that say 'Rust sucks because double linked lists lol' are morally equivalent to people that say 'C sucks because malloc and free lol', it is like.... Yeah you aren't meant to do that!
> You can always implement tagged unions in any language with untagged unions, so in a broad sense you can emulate sum types in situations where they make sense but use simpler code elsewhere
I'm a bit confused now, since I don't see how this is related to the point I was making. You are right - with the exception that sumtypes are still more powerful since you cannot e.g. emulate GADTs with tagged unions, but for most cases in practice, I agree. Still, what's the point?
I also think we have a general misunderstanding, since you are saying:
> That works in Lisp where you can elegantly add language features with proper macros
But Lisp is dynamically typed, so talking about union types is meaningless in a dynamically typed language. That doesn't make any sense to me in this context.
And about C (which is statically typed): C does not have union types (and hence also no tagged union types). What C does have are (untagged) enums, but that's not the same thing. The crucial difference is that union types are ad-hoc whereas enums are statically defined. I think it is a bit confusing since C calls them unions - but in the context of this discussion it's important that they are very different things.
E.g., with union types you can do:
type union1 = string | int
type union2 = string | boolean
type union3 = union1 | union2 | float
// same as type union3 = string | int | boolean | float
The compiler must be able to resolve those things automatically. I'm hope I'm not completely mistaken here, But I believe there is no way to combine unions like this in C at the type-level. You would have to write those out by hand or generate the code. But if there is, please correct me.
> This is compatible with old callers, even, who treat any nonzero result as failure and any zero result as complete success (the normal pattern in C).
The idea or moviation was though that in a language with sumtypes (or tagged unions) the old callers would not be compatible. Trying to compile code against `function2` should fail. But it should not fail in an arbitrary way - it should fail by the compiler saying "hey, look, you handled the error case and the success case, but you also have to handle the partial-success case; and here is how the data you need to handle looks if it is partial-success: ...". That is what sumtypes give you, and I find that this enormously useful in practice. In a language without sumtypes you will not get this level of support by the compiler - that is the point I was trying to make.
A simple syntactic check will only ever work as a heuristic. Heuristics don't work for all cases and can be noisy. The point is, no modern language should need such hacks. This problem was completely solved in the 70s with sum types.
Cries in go. I segfaulted go while learning it in the first 5 minutes. Its a a solved problem, unfit for general purpose programming on this problem class alone
Having said that, MongoDB pricing page promises 99.995% uptime, which is outstanding, and would probably be hard to beat that doing it oneself, even after adding redundancy. But maybe you don't need that much uptime for your particular use case.