https://code.lol/post/programming/higher-kinded-types/
I have to admit it's a bit heavy, but this is totally representible in today's Typescript if I understand your ask correctly. It would be great if it was a more obvious / automatic inference, though.
Or maybe, you could pass some kind of "scope" interface that provides functions for executing side effects. So "impure(IO) function printStuff()" brings all the functions in IO into scope for the body of printStuff.
More likely it would lead to most functions looking like they are pure, whether they are actually pure or not.
Still, it feels dangerous to be able to feed a generic object method in and expect the return to be typed based on a previous typing of the composite instances. Or maybe it feels like something that should always pass a compile check - because Object.entries can always be any - and only fail at runtime. Even if it was a feature, it's something I wouldn't implicitly trust a compiler to catch all edge cases of.
Not really. Typescript already has the
keyof
keyword. For some reason though Object.entries(T)
returns string[]
when it could easily return keyof typeof T
I've had to write a helper function that essentially does this for you with a generic and a cast, but it feels unnecessary.It's better to default to the best practice. What you should wish is for an `impure` keyword and then all impure functions that are not marked as such would raise a compiler warning.
But one solution might be a “noImplicitImpure” option.
If pure functions were explicitly marked as "pure" the compiler could just conservatively assume all other functions were impure. But the opposite does not work.
This is correct behavior though, and what you propose would be wrong. The type isn’t narrowed. Type U satisfies type T when the keys of U are a superset of the keys of T, so long as the keys map to the correct value-types in the intersection of (keyof T) and (keyof U)
This is because the only time extra keys are outlawed is when you instantiate an object of type T and explicitly declare it as a T. I can link if you need but this is an important subtlety which is why Object.entries returns string instead of keyof T, and it’s not borne of MS/TS developer laziness
Could they add a strictness flag that lets objects actually be type safe and enables related functions to work out of the box? For me this stuff is a regular pain point as you end up typecasting at every call site :(
It would be incredibly painful to write idiomatic JS with constant narrowing required and would lead to huge performance issues.
Imagine you write some generic sort capabilities, sorting by label, createdDate, etc., and then you declare some types
interface SortableByLabel { label: string }
interface SortableByCreatedDate { createdDate: Date }
...
Then you implement a method function sortByLabel(a: SortableByLabel, b: SortableByLabel) { ... }
I've written enough, I think you can see how easily this works today and how painful and un-performant it would be to do this if you had to narrow your objects or constantly write extra info into your types to allow them to not enforce narrowing, and then there's a whole can of worms around your "widen"-able object types being illegal to functions that require exact types, ...Instead you can just write arrays...
interface MyInterface {
key1: Type1;
key2: Type2;
...
}
const myKeys:Array<keyof MyInterface> = [
'key1',
'key2',
...
] as const;
function operatesOnKeys(object: MyInterface) {
// don't use Object.entries...
myKeys.forEach((key) => {
object[key];
// TS is happy, and bonus:
// this will be type checked later if MyInterface changes
});
}
I think TS has some issues but this isn't one of them.https://www.npmjs.com/package/ts-is-present
that particular case is a small snippet but the package also comes with helpers for reaching into objects to do a similar thing
Pipe dream, unfortunately. If pure functions accept objects as arguments, those objects could have impure getter methods, or perhaps be proxy objects that have impure methods. Pure functions could also receive Function objects, so you'd have to indicate that the passed arguments are pure as well.
Unfortunately, because TypeScript is optional, such a declaration on the functions and their arguments would also be optional, meaning that you couldn't even rely on this as a language feature (your caller could pass you an impure function even if you specified you didn't want it).
Your last argument is confusing: the same applies to everything in typescript. You can also pass in a string to a field that's demanding a number by using "as" so you never have any type guarantees in TS, there is value in it nonetheless.
And the same would surely apply to pure functions (I.e. inpureFunction as PureFunction)()...) It could still be a valuable feature anyway
But imagine that TypeScript in addition to pure functions you can also tag objects as "POD objects" or "JavaScript objects". The POD ("plain old data") objects can't be proxies or have impure getter methods, and only POD objects can be passed to pure functions. The "pure" keyword actually means "pure if all arguments are POD objects" (this is to avoid having to write N purity overloads for an N-ary function).
You want to call a pure method from your impure method, and pass it one of your arguments. Your arguments now need to be declared to be POD objects, which means they are incompatible with the rest of your code base. This has to go all the way up... it's basically "colored functions" except it's colored objects this time, and the colors are completely incompatible (async functions can call sync functions, but impure functions can't call pure functions with impure arguments, and pure functions can't call impure functions at all).
[0] https://www.typescriptlang.org/play?#code/C4TwDgpgBAkgYgewVA...
I'm aware. That doesn't make it impossible to differentiate as Webstorm shows by coloring the bar differently for fooA vs fooB in your example. But yes, it would have to be implemented as part of that pure feature.
It's especially bad to do this if you're going to perform optimizations based on those safety guarantees (thankfully, typescript doesn't do that.) You can say 'don't use the pure keyword then' but the fundamental design of JS means that in practice almost no functions can be pure. The runtime has to identify cases on the fly where pure optimizations are actually valid.
Though now I’m doubting myself because maybe it’s possible to use complex enough assertion functions to really prove that. But even if you don’t, it’s still an incredibly useful language. You’re basically saying, if X is true then the rest of this program should fit together like so.”
You're right that in the end once you pull in outside data you're having to make promises that you may not be able to keep - I just think purity is an especially difficult set of promises to keep, and the existence of proxies and getters in JS (along with how they work) makes purity basically impossible, so it's just a footgun waiting to be used. And TS isn't going to optimize your code using it, so it's not really delivering value either.
[1,2,undefined,3]
.filter(<T>(x: T|undefined): x is T =>
typeof x !== 'undefined'
);
Yes builtin lang support would be better, but that gets you out of trouble in a pinch, without adding more 5 line npm baggage.N.B. if you are in a JSX file, you'll need to add a comma: `<T,>`
Then rather than marking a function as pure you instead check it is pure by looking at what effects its function type has!
Okay and maybe I also want it to be aware of type assertions that happened outside the array functions. You should already know that x cannot be undefined anymore. I tested that. I know you don’t think you can be sure you’re actually being called after the assertion, but you are! It’s obvious!
Oookay and maybe just one more (sorry Santa!): I want a ‘pure’ keyword. Any function that is pure can only call functions that are also keyworded as pure. No side effects, guaranteed.