I learned from a friend to use Zod to check for process.env. I refined it a bit and got:
```
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'staging']),
DATABASE_URL: z.string(),
POSTHOG_KEY: z.string(),
});export type AlertDownEnv = z.infer<typeof EnvSchema>;
export function getEnvironments(env: Record<string, string>): AlertDownEnv { return EnvSchema.parse(env); }
```
Then you can:
```
const env = getEnvironments(process.env);
```
`env` will be fully typed!
Definitely, I need to do some improvements in my frontend logic!
This could be implemented with `??`, something like: `process.env.NODE_ENV ?? deferred_error(/temporary fallback/'', 'NODE_ENV not set')`, but is probably best done via a dedicated wrapper.
I am quite surprised people here doesn't know how to validate data in runtime. The author completely mixing Typescript with runtime behavior.
a?.b?.c?.() or var ?? something have well documented use cases and it's not what the author is thinking.
const env_var = process.env.MY_ENV_VAR ?? throw new Error("MY_ENV_VAR is not set");Null chaining is also a common pattern across most languages and is generally seen as readable
If you aren't going to be doing that, using const lets the reader know the assignment won't change to refer to a new value.
It's that simple.
There's a similar argument to be made for using forEach/ map / reduce / filter instead of a for loop. Each of those iterative methods has slightly different semantic intent. The only thing the for loop adds to the mix, and therefore the only reason to use them, is the availability of continue / break.
Hell, the same argument could be made for while loops vs for loops. What does a for loop really add that you couldn't get with a while loop?
As for your last point, I don't know of any linter that complains about the use of let when you reassign it. The ones I've used only complain if you use let when const would have worked identically in your code.
The fact that people write bad code is not down to let/const or linters. People always have and always will write crap and not understand why.
In 4 characters it clarifies something that could take 300 lines, 10 nested ifs, loops, etc; and, that trust disappears the moment someone, later down in the code *does* change the value.
Clarified intent, nearness-to-declaration, and linted protection are real benefits.
console.assert(maybeNull != null, 'variable not set')It's a function you could write for yourself and give it whatever semantics you feel is best. No changes to the language required for this one.
I may not understand.
> Why not cast to a non-undefined type as soon as we're sure (and throw if it is not)
This is exactly what the OP author suggests.
It's funny you bring this up because people opposed to `.unwrap()` usually mention methods like `.unwrap_or` as a "better" alternative, and that's exactly the equivalent of `??` in Rust.
> In Rust, however, you're forced to reason about the "seriousness" of calling .unwrap() as it could terminate your program. In TS you're not faced with the same consequences.
unwrap() is the error.
unwrap_or() is the fallback.
For your own types you can implement the Default trait to tell Rust what the default value is for that type.
As you, and many others, have pointed out, `?? ""` does not do what `.unwrap` does. `unwrap_or_default` would have been a better comparison for what it actually does. What I tried, and failed, to communicate, was that both can be used to "ignore" the unwanted state, `None` or `undefined`.
I guess the rust equivalent to what I would like to see is `nullable_var?`, and not unwrap as that will panic. That would be equivalent to the `?? throw new Error` feature mentioned further up in the comments.
Don’t listen to those opinionated purists that don’t like certain styles for some reason (probably because they didn’t have that operator when growing up and think it’s a hack)
```ts user?.name ?? "" ```
The issue isn't the nullish coalescing, but trying to treat a `string | null` as a string and just giving it a non-sense value you hope you'll never use.
You could have the same issue with `if` or `user?.name!`.
Basically seems the issue is the `""`. Maybe it can be `null` and it should be `NA`, or maybe it shouldn't be `null` and should have been handled upstream.
Is this a kink? You like being on call?
You think your performance review is going to be better because you fixed a fire over a holiday weekend or will it be worse when the post mortem said it crashed because you didn’t handle the event bus failing to return firstName
---
I disagree that erroring out when the app doesn't have ALL the data is the best course of action. I imagine it depends a bit on the domain, but for most apps I've worked on it's better to show someone partial or empty data than an error.
Often the decision of what to do when the data is missing is best left up the the display component. "Don't show this div if string is empty", or show placeholder value.
---
Flip side, when writing an under the hood function, it often doesn't matter if the data is empty or not.
If I'm aggregating a list of fooBars, do I care if fooBarDisplayName is empty or not? When I'm writing tests and building test-data, needing to add a value for each string field is cumbersome.
Sometimes a field really really matters and should throw (an empty string ID is bad), but those are the exception and not the rule.
It’s the difference between:
if (fooBarDisplayName) { show div }
And:
if (foobarDisplayName && foobarDisplayName.length > 0) { show div }
Ultimately, type systems aren’t telling you what types you have to use where - they’re giving you tools to define and constrain what types are ok.
My example is we want to skip the div if empty or undefined. We can't throw on assignment so we leave it as as string|undefined.
When we go to display, we have to check if the string is empty anyway, right? What if it's empty in the DB or API response?
No matter what the display-component is doing something to prevent showing the empty string.
` if(fooBarDisplayName.length) { show div } `
or
` if(fooBarDisplayName?.length) { show div } `
I'm not sure what we gain by leaving it as `string|undefined`
---
If there was a "NonEmptyString" type maybe I could see where you're coming from.
I guess you could argue treating `string` like a `NonEmptyString` type seems error prone. The compiler can't verify you've done the check. At some point someone will set a string when it's empty or not do the necessary check.
You'd want a separate non-string type to make sure it's actually been parsed
https://effect.website/docs/schema/advanced-usage/#branded-t...
There is some ceremony around it, but when you do the basic plumbing it's invaluable to import NonEmptyString100 schema to define a string between 1 and 100 chars, and have parsing and error handling for free anywhere, from your APIs to your forms.
This also implies that you cannot pass any string to an API expecting NonEmptyString100, it has to be that exact thing.
Or in e-commerce where we have complex pricing formulas (items like showers that need to be custom built for the customer) need to be configured and priced with some very complex formulas, often market dependent, and types just sing and avoid you multiplying a generic number (which will need to be a positive € schema) with a swiss VAT rate or to do really any operation if the API requires the branded version.
Typescript is an incredibly powerful language, it is kinda limited by its verbose syntax and JS compatibility but the things that you can express in Typescript I haven't seen in any other language.
i think this would be really nice if validation libraries like zod returned branded types when they are validating non-comp-time types (like z.ipv4() should return some IPv4 branded type)
Now every time you will have to use a NonEmptyString255 as a type it has to be branded by passing through the constructor, so you can't pass a normal string to an API expecting it, and you get the error at type level. The logic is encoded in the schema itself, which you can click.
And it also provided the decoder (parser) and encoder (constructor). So you use the parser in a form or whatever and get parsing and precise errors (for it being too long, too short, not a string). And you can annotate the errors in any language you want too (German, Spanish, etc, English is the default)
Essentially this approach is similar to using some class NonEmptyString without using a class and while keeping the information at type level.
It's practical and the ceremony goes as far as copy pasting or providing a different refinement, besides, AI can write those with ease and you don't need to do it frequently, but it's nice in many places not mixing UserIDs with ProductID or any other string makes codebases much easier to follow and provides lots of invariants.
i was suggesting the result of zod parse is a type that shows how it’s been refined
however, .ipv4().parse(“..”) returns a type “string”
string
type nonEmptyStr = string & NonEmpty
type ipv4Str = string & IPv4
it’s not obvious how you’d automatically determine ipv4Str is also a nonEmptyStr, since the types themselves are just labels, they don’t store the refinements at type level
<input type=“text” defaultValue={user.email ?? “”}>
The user is an entity from the DB, where the email field is nullable, which makes perfect sense. The input component only accepts a string for the defaultValue prop. So you coalesce the possible null to a string.
Consider the author’s proposed alternative to ‘process.env.MY_ENV_VAR ?? “”’:
if (process.env.MY_ENV_VAR === undefined) {
throw new Error("MY_ENV_VAR is not set")
}
That’s 3 lines and can’t be in expression position.There’s a terser way: define an ‘unwrap’ function, used like ‘unwrap(process.env.MY_ENV_VAR)’. But it’s still verbose and doesn’t compose (Rust’s ‘unwrap’ is also criticized for its verbosity).
TypeScript’s ‘!’ would work, except that operator assumes the type is non-null, i.e. it’s (yet another TypeScript coercion that) isn’t actually checked at runtime so is discouraged. Swift and Kotlin even have a similar operator that is checked. At least it’s just syntax so easy to lint…
Isn't this more like `unwrap_or`?
There’s nothing wrong with using the ?? operator, even liberally. But in cases where 1) you don’t think the left side should be undefinable at all or 2) the right side is also an invalid value, you’re better off with an if-statement and handling the invalid case explicitly.
But I’ve used ?? thousands of times in my code in ways unrelated to 1) and 2).
Trivial example:
const numElements = arr?.length ?? 0
IMO this is fine if it’s not an important distinction between an array not existing and the array being empty, and gives you an easier to work with number type.
renewiltord•2mo ago
> By doing this, you're opening up for the possibility of showing a UI where the name is "". Is that really a valid state for the UI?
But as a user, if I get a white screen of death instead of your program saying “Hi, , you have 3 videos on your watchlist” I am going to flip out.
Programmers know this so they do that so that something irrelevant like my name doesn’t prevent me from actually streaming my movies.
polishdude20•2mo ago
slooonz•2mo ago
noman-land•2mo ago
rustc•2mo ago
tldr: undefined constants were treated as a string (+ a warning), so `$x = FOO` was `$x = "FOO"` + a warning if `FOO` was not a defined constant. Thankfully this feature was removed in PHP 8.
pitched•2mo ago
jackblemming•2mo ago
mystifyingpoi•2mo ago
I hate this sentence with a passion, yet it is so so true. Especially in distributed systems, gotta live with it.
noman-land•2mo ago
Context matters a lot. If it's Spotify and my music won't play is a lot different than I filled a form to send my rent check and it silently failed.
hahn-kev•2mo ago
pverheggen•2mo ago
mystifyingpoi•2mo ago
No one suggests hard crashing the app in a way that makes the screen white. There are better ways. At least, send the error log to telemetry.