achou
Every time you find a runtime bug, ask the LLM if a static lint rule could be turned on to prevent it, or have it write a custom rule for you. Very few of us have time to deep dive into esoteric custom rule configuration, but now it's easy. Bonus: the error message for the custom rule can be very specific about how to fix the error. Including pointing to documentation that explains entire architectural principles, concurrency rules, etc. Stuff that is very tailored to your codebase and are far more precise than a generic compiler/lint error.
There are async-safe variants but the typical lodash-style implementations are not. If you want the semantics of "return a promise when the function is actually invoked and resolve it when the underlying async function resolves", you'll have to carefully vet if the implementation actually does that.
Does anyone know if the problems that Humbird describes have somehow been solved?
Invariably when I choose to behave like an adult I feel empowered and ultimately at peace with myself and others in the end. If I choose avoidance, resentment builds, and further avoidance follows.
The idea that avoidance behaviors can be selfish or agreeable cuts through much self-deception. This can be helpful when I tell myself "I'm just being nice" because it adds the proviso: "yeah, but I'm not being an adult." Which I could see being a really helpful inner monologue in those situations.
This is also intimately connected to the concept of "taking responsibility", which begins with not avoiding something which "someone else" might deal with so you don't have to.
Researchers chart path to drastically lower administrative costs of health care
Having a yard is exceptionally valuable with kids. They can be free to run around, play yard games, explore - all without draining the emotional reserves of parents. You can do this while you make dinner, work, or do other things that are unrealistic at an urban park. And you can play with them too. It is not that expensive to get someone to care for your yard if you have some financial flexibility.
In a family there are many kind of resources - time, money, energy, emotional reserves. Scarcity in the first two are easy to focus on because they're quantitative. But scarcity in the latter two - especially emotional reserves - is actually the limited resource in many families. Having to deal with the stresses of children (even if they are mostly well behaved!) puts a real strain on relationships. And the friction from an urban environment, especially one like SF which is not child-friendly, or a giant megacity that has very high density, just drains away the scarcest resources that a family like mine actually has.
If you have some financial flexibility and need serenity and quiet to be your best, combining kids with an urban environment in exchange for spending less is a very poor allocation of resources. It's an example of optimizing for the thing you can easily measure, instead of what's truly important.
(1) This issue isn't going away, no matter who wins the next election.
(2) Data collection is not the only goal or threat. The article mentions other critical systems: energy, financial, healthcare, transportation, military. Even agriculture is heavily software dependent now[1]. Also, once you depend on a cloud service, the open source used by it is brought into the attack surface.
(3) Open source is theoretically reviewable, which is good. But even if resources were brought to bear to review it at scale, you'd need to do it continually and track what has passed. This brings pressure to fork. Worse, because review is imperfect even with the best people and tools, it will never be enough by itself to establish that a system doesn't contain malicious code. Current program verification technology is simply not up to the task of formally verifying the behavior of large scale software systems. Maybe it could be used for smaller libraries.
[1]: https://www.deere.com/en/technology-products/precision-ag-te...
But what about programs that can run fine but are excluded by the type system? They are censored. This is what's less visible until you actually try to build something substantial, pushing the boundaries of expression, performance, or scale. Then you'll find that a sound type system can become more and more of a straight-jacket. One that forces you to write code in ways that are limited by what its type designers could envision expressing (or could prove that was safe to express).
The trouble is that it's hard to know when you might reach the point where the type system begins to limit you. It might never happen, as you frolick happily within the walled garden. Or it might happen when you write your first line of code. In a large system that must deal with external requirements, it's something that in my experience becomes inevitable. That's not even touching upon comprehensibility, which becomes an increasing challenge for purely sound type systems as they attempt to increase expressiveness.
The biggest innovation of TypeScript is JavaScript. That is, TypeScript started with a huge established corpus of JavaScript that showed what developers wanted to express and how they wanted to express it. And, crucially, how popular various kinds of expression actually were in the developer community. This forced TypeScript to take practical expressiveness of millions of lines of JS seriously, that ordinarily would have been censored by type system design before the first line of code was written.
That's why it's entirely unfair to harp on TypeScript's unsoundness, without also exploring the corresponding gain in expressiveness and comprehensibility relative to other type systems.
BTW, this line of reasoning is the reason so many "sound" type systems also have their equivalent of "any": Rust has unsafe[1]. C# has unsafe[2]. Flow has any[3]. And all practical languages have strings, which are kind of a lowest common denominator when it comes to dealing with a type system that simply can't express what you need.
[1]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html [2]: https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... [3]: https://flow.org/en/docs/types/any/
(() => Promise<void>) | undefined
admittedly it may not be all that common to have a function-valued variable that may be undefined, but it happened in the code base I was working with.In the last example, you're right that TypeScript will catch this at compile time. My point was to show how this compile time error can happen from refactoring to use optional chaining, and one easy solution in this case.
foo && await foo();
is not the same as await foo?.();
this will work in most cases but subtly, the await wraps the undefined case into a Promise, while the original code would skip the await altogether.String regular expression matching returns null, not undefined, so rewriting code such as:
const match = str.match(/reg(ex)/);
return match && match[1];
is not the same thing as: return match?.[1];
because the latter returns undefined, not null, in case of match failure. This can cause problems if subsequent code expects null for match failure. An equivalent rewrite would be: return match?.[1] ?? null;
which is longer than the original and arguably less clear.A common idiom to catch and ignore exceptions can interact poorly with optional chaining:
const v = await foo().catch(_ => {});
return v?.field; // property 'field' does not exist on type 'void'
This can be easily remedied by changing the first line to: const v = await foo().catch(_ => undefined);
Of course, these new operators are very welcome and will greatly simplify and help increase the safety of much existing code. But as in all things syntax, being judicious about usage of these operators is important to maximize clarity.This applies to the microservice/monolith debate as well. And it basically boils down to the observation that having lots of shallow services doesn't really reduce complexity. Each service may be simple unto itself, but the proliferation of many such services creates complexity at the next level of abstraction. Having well designed services with a simple API, but hide large amounts of complexity beneath, really reduces cognitive load for the system as a whole. And by "simple API" I think it's important to realize that this includes capturing as much of the complexity of error handling and exceptional cases as much as possible, so the user of the services has less to worry about when calling it.
[1]: https://www.amazon.com/Philosophy-Software-Design-John-Ouste...
In local mode, a process is created up to the concurrency limit you specify, and each process is reused for subsequent calls (mimicking how Lambda reuses containers, allowing you to use the same caching behavior you'd use on Lambda). I'm not currently using webworkers, but that's something I could see a new mode for easily. For larger data, I would recommend storing arguments and return values directly in cloud storage like S3, or on local disk in local mode.
I would be interested to learn how your experiment with faast.js goes!
If a function fails to execute for transient reasons and exceeds the retry maximum (a config setting you can change), then it will reject the return value promise. You can catch that and handle with another attempt, or report an error, or just ignore it and report less accurate or complete results.
One special case is if your functions return a lot of data; outbound data charges can get expensive fast, and you'll be limited in getting responses by your network link. So you can run the coordinator code on, say, EC2 in the same region and then the link to Lambda is super fast and you won't have any outbound data costs.
Faast.js can be used with multi-core, just use the "local" mode and run it on a large box. I'm billing this as a way to test locally before running in the cloud, but it's actually a completely viable way to run parallel processes on one machine, with the option to run on serverless with a one line change.