News for hackers. Stuff that matters.
Re:TypeScripting the Technical Interview by joenot443 on Sunday March 12, 2023 @12:27PM (#35122121)
Well written and super funny. Reminded me a bit of Scott’s writing, particularly the descriptions of the horrified interviewer.

I’ve never worked in a Typescript shop, is there any truth to the satire here? The sea of confusing types to solve any problem?

Re:TypeScripting the Technical Interview by AgentOrange1234 on Sunday March 12, 2023 @12:36PM (#35122287)
It’s a very beautiful language even despite being compatible with Javascript. The code here is delightfully absurd, please don’t think it is representative.
Re:TypeScripting the Technical Interview by nayroclade on Sunday March 12, 2023 @12:49PM (#35122486)
I’d say generally the opposite is true. Most commercial software I’ve worked on typically contained only simple typing—discriminated unions are about as complex as it gets—and it’s more of a problem that people get lazy and start using “any” too much than they go overboard.

Where complex types can be a problem is when working with open source libraries, especially when the types are community-developed, separate to the library itself. The library API may not be particularly amenable to easy typing, and the community types can end up being rather confusing, especially to people who developed neither the types nor the original library.

Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @12:53PM (#35122572)
I tell my fellow developers at work: "Any is banned. If you want any, use JavaScript, and we don't use JavaScript here. Perhaps you haven't heard about unknown?"

In my experience, 90% of the time when a developer uses any, they just don't know about unknown. 9% it's because they are lazy. 1% is because you are implementing something from an imported library, and they fell into the other 99%.

Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @01:11PM (#35122848)
I make an exception for using any in type params which extend type params, eg

  const foo = <T extends Record<string, any>>(dict: T) => …
This is a good signal that foo maps over dict in some generic way that cares more about its dictionary-ness than its values. Sure, unknown works in that position too, but at least IMO the “doesn’t care” bit is more informative than “doesn’t know”. The latter might imply more type narrowing will happen than is the case.
Re:TypeScripting the Technical Interview by alexgrover on Sunday March 12, 2023 @02:03PM (#35123727)
The problem with that is that when consuming of the dictionary, “doesn’t know” is actually more appropriate. If you then access Object.values(foo) in your method you are given an iterable of anys which is unsafe.
Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @03:34PM (#35125081)
If the function is doing something with the values which is unsafe, sure. My point was the more relaxed constrain on the type signature can be used to imply it’s only concerned with the dictionary’s keys.
Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @07:48PM (#35128345)
Then use unknown. Either you know what's in the dictionary and can type it or you don't. Stop being lazy.
Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @08:01PM (#35128512)
I’m not being lazy? I am taking extra time and expending extra energy to make sure the metadata I put in code is as informative as possible. In this use case `any` carries more information. I use `unknown` in place of `any` exactly as it’s intended wherever I’m able.

Also, please don’t continue to be a jerk at me.

Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @08:10PM (#35128615)
Okay I'll stop being a jerk and engage in good faith. I'll assume you think this is a valid use case for any and it communicates something important.

My counterpoint is this: communication involves two things, someone stating a message and someone receiving a message. You are doing part one. Is part two occurring? It may be because of certain conventions in your codebase or team, but I've personally never seen any used in that manner ever, so I would not receive your intended message.

If I were in the same situation I would use unknown and add a comment stating that the type is of no importance since I'm only worried about the keys. That way my message is clear and I prevent future developers from having to debug code where they assume the value is of a certain type and start accessing parameters and methods that do not exist.

Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @08:19PM (#35128729)
> You are doing part one. Is part two occurring?

Valid feedback. I even thought of adding it myself, because implied stuff isn’t obvious. I felt it worth communicating because there’s value in what’s implied that isn’t available in the type system. To the extent I have team members consuming the same code, I would definitely communicate the intent. To the extent I have reviewers who read the code, I do discuss it.

To the extent this is in a type parameter position, the onus is on the person writing the function signature and… well if they don’t want a footgun, they have every opportunity to not gun their foot. But that’s entirely opt in by the time they’ve reached that point.

Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @08:32PM (#35128884)
I think at this point we'll agree to disagree.

I will offer this as a middle ground that I'm not even 100% sure will work since I'm not in front of a TypeScript interpreter.

What about just defining it as object? Would work for object.Keys, but not sure how the function consumers would get along with it.

Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @09:51PM (#35129678)
> What about just defining it as object?

That’s pretty much the intent of the constraint. I don’t have time to sit with a type checker right now, but I don’t think we disagree as much as you might think. I arrived at this from years of trying to find the best way to express types which are as strict as possible with as much clarity as possible.

Unfortunately the object type is basically any non-null value, as is {}. They both intuitively mean what I want. They also inherently allow PropertyKey keys, which is effectively Record<string | number | symbol, any>, which is looser than the “dictionary” type I often want to accept in these scenarios.

A better question (for me, and maybe you and maybe all of us who want type certainties) is why we even accept dictionaries in object shapes when Map is the obvious expression of that type. I’ve repeatedly wanted that and shied away from it because it requires too much change for very little gain.

Re:TypeScripting the Technical Interview by WorldMaker on Tuesday March 14, 2023 @04:35PM (#35158084)
I think with Map the answer is somewhere between momentum ("we've always used objects as dictionaries") and some misapprehensions easily shifted with basic caniuse statistics. Map still feels "too new" to some developers, despite being ES2015 (8 years old now!) and available in every browser that supports the arrow operator for functions has Map (and Set) out of the box (no need for polyfills in 2023, ever).

Probably the only other reason I've seen is "JSON interop" is "hard" because Map doesn't natively serialize. I think `new Map(Object.entries(oldDictionaryObject))` and `Object.fromEntries(someMap.entries())` are sufficient for most serializer boundary cases (even without feeling fancy and doing that as a true JSON revivifier/resolver pair).

Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @09:58PM (#35129764)
Completely aside from these details, thank you for stepping back and discussing this in good faith. It made a discussion that was going badly feel at least like communication. I appreciate that, and I’m glad to land at a place where we’re not necessarily on the same page but we’re at least recognizing we have similar priorities. Cheers!
Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @08:17PM (#35128705)
As an addendum if the function only needs the keys I would possibly just have the parameter be a string[] that expected the user to call object.Keys to pass to.

That way the function isn't asking for parameters it doesn't really care about.

Though I do get the appeal of having the function call object.Keys if it's called frequently so as not to have to sprinkle that call everywhere.

Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @08:20PM (#35128754)
Yeah unfortunately it’s ergonomically A Thing to just accept object as input even if you only care about keys. Otherwise I’d have the exact signature you describe.
Re:TypeScripting the Technical Interview by alexgrover on Tuesday March 14, 2023 @02:11PM (#35155923)
Coming back to this after the other thread cooled down a bit lol - to me, unknown actually implies that the function doesn’t care about the values more than any, as the compiler will enforce that they’re unused.

And in any case, I’d almost always lean towards the option with stronger type safety guarantees. Especially in a team environment when someone else may be modifying your code later. As a convention, I almost never use any.

Re:TypeScripting the Technical Interview by eyelidlessness on Thursday March 23, 2023 @10:07PM (#35284162)
To me `unknown` signals an intent to know, as in “I don’t know yet” (or put another way, “I don’t have any prerequisites for accepting this value, I can and will narrow it as appropriate”). In a codebase that otherwise takes type safety seriously, `any` in a type parameter (again to me) means “I don’t have any type narrowing agenda for this thing, it’s along for the ride and coming out the other side the same way it showed up”.
Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @07:54PM (#35128412)
Any doesn't mean "doesn't care". Any means "YOLO, do whatever you want, I'm one of those cool parents who'll let you smoke and drink beer."
Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @07:57PM (#35128453)
What is the distinction?
Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @08:01PM (#35128508)
The distinction is just because you "don't care" about the values right now, nothing stops the next developer (including future you) from needing the values.

So now you've gone from not caring to "enabling someone to shoot themselves in the foot" if they don't read the types of the parameters carefully. That's the difference.

Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @08:04PM (#35128545)
The case I was trying to convey doesn’t narrow the actual type for anything outside its own function body. Any footgun that exists after the function call already existed before it. It just implies “I’m only looking at your keys not your values”.
Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @08:13PM (#35128653)
I gathered what you meant. My point is what stops the next developer working on your codebase from adding code in the body of your function that accesses the values of the object in an unsafe way?
Re:TypeScripting the Technical Interview by eyelidlessness on Sunday March 12, 2023 @08:22PM (#35128771)
A bit of trust. I’m not aware of a type system that protects my coworkers from my bad decisions, only those humans do that.
Re:TypeScripting the Technical Interview by consilient on Sunday March 12, 2023 @11:16PM (#35130540)
> It just implies “I’m only looking at your keys not your values”.

That's what `unknown` is for. `any` behaves like `never` (in covariant positions), which is exactly the opposite.

Re:TypeScripting the Technical Interview by eyelidlessness on Monday March 13, 2023 @01:26AM (#35131470)
I don’t think that’s how `any` behaves in any position, but I’d be happy to be corrected. Please show me how `any` is treated as `never`.
Re:TypeScripting the Technical Interview by consilient on Monday March 13, 2023 @02:47AM (#35132004)
Here's a close to minimal example:

    const anyToNeverHelper = <T,>(t: any): number & T => t
    const absurd: never = anyToNeverHelper<never>(0)
Re:TypeScripting the Technical Interview by eyelidlessness on Monday March 13, 2023 @03:26AM (#35132291)
This is a good example of how `never` is treated as `never`. Everything is assignable to the bottom type and intersecting with it will always be the bottom type, by definition. It’s also a good example of how the top type `any` casts to whatever you choose, because that’s also by design. Both types are vacant, the bottom type is infectious. That’s a good thing. And it works the same way with `unknown`, which it also should because once something is known to be part of the null set it should stay known as the null set.
Re:TypeScripting the Technical Interview by consilient on Monday March 13, 2023 @05:07AM (#35133035)
> Everything is assignable to the bottom type

Other way around: everything is assignable to the top type (`unknown`), and the bottom type (`never`) is assignable to everything.

> It’s also a good example of how the top type `any` casts to whatever you choose

Which is precisely why `any` isn't the top type: if you allow `any`, then types no longer form a lattice, and there is no bottom or top.

If you restrict yourself to a sound fragment, then `unknown` is the top type. Compare `(x: unknown) => boolean` (two inhabitants up to function extensionality) to `(x: any) => boolean` (infinitely many inhabitants).

> And it works the same way with `unknown`, which it also should because once something is known to be part of the null set it should stay known as the null set.

But `unknown` isn't the null set: it's the "set" (insofar as we're pretending that types are sets of values, which isn't quite true) of all terms.

Re:TypeScripting the Technical Interview by eyelidlessness on Monday March 13, 2023 @12:47PM (#35138368)
> Other way around: everything is assignable to the top type (`unknown`), and the bottom type (`never`) is assignable to everything.

Yep, sorry that’s what I meant.

> Which is precisely why `any` isn't the top type: if you allow `any`, then types no longer form a lattice, and there is no bottom or top.

I’m not sure I understand.

> If you restrict yourself to a sound fragment, then `unknown` is the top type. Compare `(x: unknown) => boolean` (two inhabitants up to function extensionality) to `(x: any) => boolean` (infinitely many inhabitants).

Sure, but I was referring to an exception I make for ignored parts of a type, in a type param. This example would be more analogous as:

  <T extends (x: unknown, ...rest: any[]) => boolean>
Which I hope makes my exception more clear, even if you don’t agree with it. It hopefully communicates that x is of interest and rest is not.

> But `unknown` isn't the null set: it's the "set" (insofar as we're pretending that types are sets of values, which isn't quite true) of all terms.

I was referring to never as the null set. Once you narrow anything—any, unknown, etc—to never, you can’t widen it to anything (without an unsafe cast of course).

Re:TypeScripting the Technical Interview by oblak on Sunday March 12, 2023 @01:01PM (#35122707)
Use "unknown" and TS complains that you're treating something as an object.

Use Object.defineProperties and TS complains because that stuff is invisible to it after how many years?

I think you're right, of course, but TS is hardly perfect and treating its ways as gospel is not an improvement over JS. The "right ways" change over time and beliefs are not shared among everyone.

Re:TypeScripting the Technical Interview by TheCleric on Sunday March 12, 2023 @07:51PM (#35128389)
If you know what's in the object cast it as that type or be sure by saying if ('propertyName' in unknownObject).

TS is far from perfect. These aren't its ways (it provides any, so of course it's fine with it). These are my restrictions: if you're using a type system, actually use it. Don't lie to yourself and throw anys in your code.

Re:TypeScripting the Technical Interview by beebeepka on Monday March 13, 2023 @01:51PM (#35139576)
any is the last resort.

How do you get around properties assigned via Object.defineProperties recognised by TS, though? I really don't know. Is is an unrelated question

Re:TypeScripting the Technical Interview by TheCleric on Monday March 13, 2023 @05:08PM (#35142687)
Here's how I would do it:

    interface IOptionsX {
        x: number;
    }
    
    interface IOptionsY {
        y: number;
    }
    
    const testObj: IOptionsX = {x : 0};
    
    Object.defineProperties(testObj, {
        y: {
            value: 100,
            writable: true
        },
    });
    
    if ('y' in testObj) { // Could also do `&& typeof testObj.y === 'number'` to be VERY sure.
        const testObjWithY = testObj as typeof testObj & IOptionsY;
        console.log(testObjWithY.y)
    }
Re:TypeScripting the Technical Interview by TheCleric on Monday March 13, 2023 @10:46PM (#35146726)
Though honestly I've gotten this far without EVER using defineProperty so I'd probably continue that streak.
Re:TypeScripting the Technical Interview by hnfong on Sunday March 12, 2023 @01:05PM (#35122757)
> is there any truth to the satire here?

As long as you're satisfied with the answer being shown on a tooltip when you hover over a variable... sure.

Notice how the whole type structure ending up as 4 lines of inconsequential javascript after going through the typescript compiler at the very end.

Re:TypeScripting the Technical Interview by davedx on Sunday March 12, 2023 @01:11PM (#35122837)
I’ve worked for TypeScript companies for a while now. Most devs I’ve met are fairly pragmatic and wouldn’t try something like this for production code, but I’ve definitely met a couple who have an exceedingly deep grasp of the type system and would appreciate the whimsy of it.
Re:TypeScripting the Technical Interview by sebazzz on Sunday March 12, 2023 @01:28PM (#35123128)
> The sea of confusing types to solve any problem?

Mostly in typings either provided by the library itself or via the 3rd party DefinitelyTyped project. Some typings have been made so complex, that it is hard to follow what kind of concrete type is exactly expected or allowed.

[1]: https://github.com/DefinitelyTyped/DefinitelyTyped

Re:TypeScripting the Technical Interview by tengbretson on Sunday March 12, 2023 @02:30PM (#35124148)
In my experience, even the more wild/exotic patterns in typescript tend to flatten into something rather readable at their usage site. 95% of the time when I use a library that does anything close to what is done in this article I write my code, hover over it, intellisense tells me its type and I say "wow, how was it able to do that? Cool!" And I carry on.
Re:TypeScripting the Technical Interview by Aeolun on Sunday March 12, 2023 @07:20PM (#35127980)
I daresay the forest of types to validate our endpoint input/output is intimidating to many, but this post is on a whole different level.
Re:TypeScripting the Technical Interview by xsmasher on Monday March 13, 2023 @02:23PM (#35140124)
This is "stunt programming," similar to shooting an arrow into a target from horseback in a rodeo. It has little to do with the real-life business of real-life cowboys.

In this case it's using the type system to calculate the answer to a problem, which is not useful because the type system can't output anything to the console or do other IO. The "answer" will only be visible in your IDE.

The type system is powerful and extremely capable because it had to support existing javascript patterns, like "this function takes a parameter that might be a string or might be a number or might be an array" and make them type-safe.