HN user

achou

1,166 karma
Posts6
Comments68
View on HN

Y'all are sleeping on custom lint rules.

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.

Debounce 12 months ago

One thing to watch out for when using debounce/throttle is the poor interaction with async functions. Debounced/throttled async functions can easily lead to unexpected behavior because they typically return the last result they have when the function is called, which would be a previous Promise for an async function. You can get a result that appears to violate causality, because the result of the promise returned by the debounce/throttle will (in a typical implementation) be from a prior invocation that happened before your debounce/throttle call.

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.

Sure, I think what I noticed was that even idiomatic OCaml code was relatively fast, maybe 2-3x slower than C, but plenty fast enough. Whereas I was under the impression that idiomatic Haskell was far more likely to have unexpected and harder to fix performance issues (e.g. requiring more of an architectural rewrite) because of its lazy evaluation model.

What about performance? I wrote my thesis in OCaml and my recollection was that it had an amazing native code generating compiler that not infrequently output code that was almost as fast as C if you wrote it the right way. The story I heard about Haskell was far different at the time (admittedly decades ago).

David Humbird's 2021 paper "Scale-up economics for cultured meat"[1] is a pretty damning study of the problems with lab-grown meat. His core conclusion: "Capital- and operating-cost analyses of conceptual cell-mass production facilities indicate economics that would likely preclude the affordability of their products as food."

Does anyone know if the problems that Humbird describes have somehow been solved?

[1] https://onlinelibrary.wiley.com/doi/10.1002/bit.27848

Keep in mind that "avoidance" in this context refers to not confronting one's own feelings and intuition. After grappling with that feeling explicitly, avoiding overt conflict can certainly be an adult decision to make.

This is a useful analysis of what "being an adult" means. I've noticed that the moment when I feel like avoiding social discomfort or potential conflict as the precise moment when I have a choice: either be an adult and understand what I want and communicate it, or avoid it and dislike myself and project those feelings onto others.

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.

How does this culture work at the boundary? How does it work when meeting with people outside Amazon? I'm all for this idea but I can see it quickly breaking down for certain key roles, like product managers, who often interface with customers.

In the last 18 months I've lived with 2 kids in a condo in SF with a car, a downtown condo in an Asian megacity without a car, and in suburban silicon valley with a car and large yard. For us, by FAR the best quality of life has been in the suburbs.

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.

Three points:

(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...

In type systems there's a tension between expressiveness, soundness, and comprehensibility. A sound type system must exclude all programs that have runtime issues, and, holding that constant, try its best to maximize expressiveness and comprehensibility of the programs that can be written.

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/

TypeScript 3.7 7 years ago

The first example can happen in TypeScript; foo has type

  (() => 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.

TypeScript 3.7 7 years ago

I just did some refactoring on a medium size code base and here are a few things to watch out for when adopting optional chaining and the new null coalescing operator:

  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.

I think the best single observation about cognitive load is in Ousterhout's book A Philosophy of Software Design[1]. In the book he promotes the idea that classes should be "deep", such that their top-level surface API is small relative to the complexity they hide underneath.

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...

The serialization/deserialization is just JSON for now, though I plan on adding some configurability and perhaps changing the implementation at some point. There is some runtime checking to make sure the arguments are correctly serializable.

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!

Functions need to be idempotent, so you have to assume they will be retried. Faast.js will proactively do retries in some cases where it thinks a function is slow, to reduce tail latency.

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.

You're basically correct, and thanks for the suggestion to add documentation about deployment in production.

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.

Very cool. What kind of data was it, if you don't mind sharing?

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.

It depends on the specific use case. Some of the use cases I envision have sharp spikes in demand, and serverless can provide better service and price/performance. Part of faast.js is a cost analyzer that can tell you in real time how much your workload costs. What I found is that most people are probably using the wrong memory sizes for their lambda functions to optimize for price/performance. More on that when I write my next blog post... If you want a preview, check out this chart from the documentation: https://faastjs.org/docs/cost-estimates

Hi everyone, faast.js is a library that allows you to use serverless to run batch processing jobs. It makes it super easy to run regular functions as serverless functions. This is one of my first open source projects and I'd be happy to answer any questions here.

A question for those of you in France (or Europe more generally) - many of the changes being proposed -- universal healthcare, free or low cost education, paid for with higher taxes on the wealthy -- basically already exist in your country.

Yet France has seen the yellow jackets, and there seems to be a feeling of discontent that's spread far and wide throughout Europe. So my question is, why? And what does that bode for the US if we move in that direction?

(BTW, out of curiosity I looked up taxes in France and I didn't realize that it had a real wealth tax, which goes up to 1.5% of all assets: https://en.wikipedia.org/wiki/Taxation_in_France).

Yes, exactly. You, the rest of the world, are indeed subsidizing this system to some extent, and in exchange, far fewer of your own young people are sitting in garrisons around the world, doing push-ups, playing with guns and yelling each other.

I won't defend US incompetence wrt infrastructure. But I will observe that "spending more" hardly seems to be the problem, as we seem to do plenty of that already. It's that (lack of) stuff we get in return that's the problem.

As far as global "peace", it is certainly true there is still conflict, but from the bigger picture perspective it's been a relatively peaceful half century.

Again I'll point to this chart: https://ourworldindata.org/war-and-peace. Consider that the y-axis is log scale, and contemplate the WWI and WWII bubbles at the top.