Learn how to unleash the full potential of the type system of TypeScript(type-level-typescript.com) |
Learn how to unleash the full potential of the type system of TypeScript(type-level-typescript.com) |
type Expect<T extends true> = T;
`T extends true` puts a type constraint on the parameter, which then needs to be assignable to the literal type `true` to type-check.
this kind of stuff is often confusing when working with teams. using simple dumb stuff is always the better option when you can.
P.S. You might like this http://beza1e1.tuxen.de/articles/accidentally_turing_complet...
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends < T >() => T extends Y ? 1 : 2 ? true : false;
Understanding this requires a bit more context, but I'll explain why we need something so complicated in the Advanced Union Types chapter :)
I picked it from https://github.com/type-challenges/type-challenges which is an awesome resource too
The drawback of a powerful type system is you can very easily get yourself into a type complexity mudhole. Nothing worse than trying to call a method where a simple `Foo` object would do but instead you've defined 60 character definition of `Foo` capabilities in the type system in the method signature.
Less is more.
If you can get a module to do the same thing with a simpler interface, then that's generally a better module; it's typically a sign of good separation of concerns. Complex interfaces are often a sign that the module encourages micromanagement of its internal state; a leaky abstraction.
A module should be trusted to do its job. The only reason a module would provide complex interfaces is to provide flexibility... But modules don't need to provide flexibility because the whole point of a module is that it can be easily replaced with other modules when requirements change.
The advantage of the U.K. plug is that live pins are physically blocked and only released when the Earth pin is present. This is why the Earth pin is slightly longer on U.K. plugs and why insulated devices have a plastic Earth pin rather than no pin at all. The advantage of this is so you cannot jam things into them (either accidentally or intentionally) without the Earth pin. Thus making the plug much safer.
I’ve found U.K. plugs to be much more secure inside the socket too. US plugs often come away from the wall when there is a little bit of weight or tension on the plug. U.K. plugs require a great deal more pressure to come loose from the socket.
If I were to bring this back to types I’d say one needs to evaluate what the requirements are: safety or convenience.
It's even better than it looks in Europe: there are actuality 3 contact points but only 2 are salient which allows for 2 easily pluggable positions (rotate 180deg). The ground is positioned twice for that matter.
Only France has a variation around that to my knowledge, that is still compatible across Europe.
And no one notices and just plugs in and out without thinking twice about it.
There are some unsung heroes here.
Speaking strictly on plugs - while I agree it is simpler/easier - I disagree that EU plug is better.
For the bulk and lack of directionality - significantly safer (pin lengths, shielded to tip), requires a ground pin even if dummy, carries 13A easy!
The safety aspects are incredible[0]. I do not worry about my small children at all.
Lived in HK where the EU (China) and UK (HK) are pretty common in one household.
I see way too many folks trying to use Omit<>(…) and Partial<>(…) creating absolute typing monstrosities. Feels like typing duct tape and it’s impossible to read the type definition when it’s generated in a tooltip.
But what is actually inside the HTTP-payloads can then have many constraints on them which are not declared anywhere. For instance your code might assume the payload is JSON with several required fields in it.
This is where a lot of developers go overboard - not just in type systems, but in general. They are so afraid of duplication, they over-generalize and end up in a quagmire of unreadable overly complicated code.
Some duplication is easy. It's just code volume, and volume shouldn't be as scary as complexity.
I prefer some duplicate lines over having to go back and forth over some source files only because some developers think that less code is better code.
The code we create should be made for humans to read, no to machines and specially not to brag about how clever is our code
Once types get so complex, I’ve no idea what’s going wrong.
Today I had code running fine but throwing errors all over the place because some deeply nested type mismatch between two libraries.
I just any’d it… i aint got no time for that shit
But just like you probably struggled with and overcame many things before, it will be the same now. It's just that you can opt out of the typesystem in typescript whereas you are forced to learn how to deal with the runtime.
But if you make it, your development experience will change drastically. The time might be very well spent.
I kinda agree - often no amount of "this is a bad idea" will teach as well as just letting someone make the mistake and actually experience the consequences.
The only problem is that hard to maintain code often does not cause any problems until you write a critical mass of it and end up trying to develop enough non trivial extra features on top of it.
It's a matter of choosing a solution that is clear, easy to understand, and easy to maintain. There are nearly limitless solutions that can fit that definition.
Restraint comes into play because devs tend to "treat every problem like a nail when they have a hammer". When devs learn new concepts, they often look for places to use that concept even when it's a bad fit.
An example of this is excessive use of inheritance when simpler types fit better. Many of us have dealt with the greenhorn that creates a giant inheritance tree or generic mess after they first learn that "neat" concept.
The reasons OP encourage restraint might be the mental overhead of understanding what's "correct", as well as needing to rely on not only yourself but other people to be correct.
Sometimes simple is faster and harder to screw up.
I feel like readable dynamically typed code is more easily "trained" onto younglings than typed equivalent.
I understand no typings allow for much much worse code bases, but my experience has been the opposite.
The devs now have guardrails in place to make sure they follow the spec...
Advanced types are invaluable when you are writing a framework or library... But in every day implementation, I agree they should be used sparingly.
I have 7 years of ts experience and I'll still 'as any' a reduce function from time to time
Ignoring the 60 different character definitions isn't going to make the problem that you have 60 possible variants go away just because you didn't type it.
Give someone (particularly a developer) the opportunity to build something complicated and undoubtedly they will. So now you have two problems, the complicated program that actually does some hopefully useful work, and another complicated program on top of it that fills your head and slows you down when trying to fix the first complicated program. You may say 'ah yes, but the second complicated program validates the first!'. Not really, it just makes things more complicated. Almost all bugs are logic bugs or inconsistent state bugs (thanks OOP!), almost none are type bugs.
However, static analysis of existing code (in Javascript), without having to write a single extra character, may well have great value in indicating correctness.
Edit:
> TypeScript's type system is a full-fledged programming language in itself!
Run! Run as fast as you can! Note that this 'full-fledged programming language' doesn't actually do anything (to their credit they admit this later on)
Edit2:
> [...] is a type-level unit test. It won't type-check until you find the correct solution.
> I sometimes use @ts-expect-error comments when I want to check that an invalid input is rejected by the type-checker. @ts-expect-error only type-checks if the next line does not!
What new level of hell are we exploring now??
I am genuinely afraid and I'm only halfway through this thing. What's next? A meta type level language to check that our type checking checks??
> 3. Objects & Records
> COMING SOON!
> this chapter hasn't been published yet.
Thank God, I am saved.
Just today I was looking at the type definition for a third-party lib (ramda)... what the heck does this even mean...
compose<V0, V1, V2, T1, T2, T3, T4, T5, T6>(fn5: (x: T5) => T6, fn4: (x: T4) => T5, fn3: (x: T3) => T4, fn2: (x: T2) => T3, fn1: (x: T1) => T2, fn0: (x0: V0, x1: V1, x2: V2) => T1): (x0: V0, x1: V1, x2: V2) => T6;
Got it?
I don't know ramda, but I assume this is only part of the type definition of compose and this is just the longest part of it. I think compose is written in such a way that it can accept a many conversion functions as you want and this is just the longest variant that is encoded in the types.
let compose f5 f4 f3 f2 f1 f0 x0 x1 x2 = f5 (f4 (f3 (f2 (f1 (f0 x0 x1 x2)))))
whose signature is,
val compose : ('a -> 'b) -> ('c -> 'a) -> ('d -> 'c) -> ('e -> 'd) -> ('f -> 'e) -> ('g -> 'h -> 'i -> 'f) -> 'g -> 'h -> 'i -> 'b = <fun>
And even that verbose signature is better than none imo.
I also recommend type-challenges: https://github.com/type-challenges/type-challenges
It works great with the VSCode extension.
What do you mean? Which extension?
Showing fully resolved types in Intellisense would be the single largest usability enhancement they could make for me right now..
I know the universe at large has moved away from eclipse, but I loved their rich tooltips where you had nice structured representation (not just a blob of text from lsp) and could click through and navigate the type hierarchy.
Although, as a Haskell developer, I am curious what type system TS is using (System F? Intuitionist? etc) and what limitations one can expect. Aside from the syntax of TS being what it is, what are the trade-offs and limitations?
I was under the impression, and this was years ago -- things are probably different now?, that TS's type system wasn't sound (in the mathematical logic sense).
As a consequence, it has aspects of structural types, dependent types, type narrowing, and myriad other features that exist solely to model real-world JavaScript.
As far as soundness: it's not a goal of the type system. https://www.typescriptlang.org/docs/handbook/type-compatibil...
This means that it's impossible to write this function:
function isStringType<T>(): boolean { return ... }
const IS_STRING: boolean = isStringType<string>();
At best you can do something like this, which is inconvenient for more complex cases: function isStringType<T, IsString extends boolean = T extends string ? true : false>(isString: IsString): boolean { return isString }
const IS_STRING_1: boolean = isStringType<string>(true); // compiles
const IS_STRING_2: boolean = isStringType<string>(false); // type error
You basically need to pass the actual result that you want in and just get a type error if you pass in the wrong one. Still better than nothing.Link if you want to play with it online: https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABDA...
Put another way, you can't do reflection with TypeScript.
You can write that function in C++ templates, and I naively assumed that it's possible in TypeScript too, since from my observations TypeScript allows complex typing to be expressed easier in general than C++.
Can't get enough of the fireworks!
Maybe finishing one of the more advanced chapters would be enough to lure people who are more experienced to check back on progress / pay / whatever you want traffic for.
If the author is reading this, the proposed solution to the `merge` challenge is:
function merge<A, B>(a: A, b: B): A & B {
return { ...a, ...b };
}
That's the "obvious" solution, but it means that the following type-checks: const a: number = 1;
const b: number = 2;
const c: number = merge(a, b);
That's not good. It shouldn't type check because the following: const d: number = { ...a, ...b };
does not type check.And I don't know how to express the correct solution (i.e. where we actually assert that A and B are object types).
Also, looking forward to further chapters.
We have come far.
Previously, I wrote my route definitions with types for both path params and query params in one file, and used TypeScript to enforce that the equivalent back-end definitions (async loaders etc.) and front-end definitions (React components) were kept in sync.
When I first implemented this in a previous project, I found many instances where routes were expecting query params but they were being dropped off (e.g. post login redirects).
Supporting things like parameters for nested routes certainly means the TS types themselves are non-trivial, but they're the kind of thing you write (and document) once, but benefit from daily.
Examples of stuff that can and should be 100% type checked:
// ...
template: {
path: idPath("/template"),
subroutes: {
edit: { path: () => "/edit" },
remix: { path: () => "/remix" },
},
},
onboarding: {
path: () => "/onboarding",
query: (params: { referralCode?: string }) => params,
subroutes: {
createYourAvatar: { path: () => "/createyouravatar" },
},
},
// ...
Routing: // Path params
navigate(routes.template.edit({ id: props.masterEdit.masterEditId }));
// No path params, just query params (null path params)
navigate(routes.onboarding(null, { referralCode }))
// Nested route with query params (inherited from parent route)
navigate(routes.onboarding.createYourAvatar(null, { referralCode }))
React hooks: // Path params
const { id } = useRouteParams(routes.template.edit)
// Query params
const { referralCode } = useRouteQueryParams(routes.onboarding);
API routes: // ...
play: {
path: () => "/play",
subroutes: {
episode: {
path: idPath("/episode"),
},
},
},
// ...
Relative route paths (for defining nested express routers): const routes = relativeApiRoutes.api.account;
router.post(routes.episode(), async (req: express.Request, res: express.Response) => {This seems like a great time to bring up "Why Static Languages Suffer From Complexity"[1], which explains the "statics-dynamics biformity" that leads to languages like TypeScript that are actually two languages: the runtime one and the compile-time type-system one.
[1] https://hirrolot.github.io/posts/why-static-languages-suffer...
After so many years of JS programming, moving to a company that uses TS extensively (in a huge scale) feels life changing. You don't even know the effect until you use it daily. Even so, daily usage of typescript at a large web application doesn't seem to be using its full potential. I feel like libraries creator and maintainer use them more in the definitions that they created (i.e redux toolkit type is mind blowing).
Thanks for creating this lesson, it will definitely teach me a lot
Particularly enjoy the confetti :)
Massive productivity boost, and I have a kind of confidence in my code that I never have had before, not having used a strongly typed language before.
I've been coding C++ most of my life, and I must say, TS is starting to look more and more like C++ (which definitely isn't a good thing because it lures programmers into complexity).
Maybe it's because I came from C++.
Like in C++, I like the flexibility of not being too under-typed (impossible to not break anything) or over-typed (impossible to do anything).
What? No it frees-up my mind and speeds me up.
The mental gymnastics I have to engage in to work on large JS projects without TypeScript is unbearable. I have to switch between the two often and it’s night and day.
Typescript isn’t a “now you have two problems” anymore than types in any other language are.
The whole point of using types is making inconsistent states type bugs. Types are logic.
This article presents a great example: https://fsharpforfunandprofit.com/posts/designing-for-correc...
Most, possibly all, inconsistent state bugs and many logic bugs are type bugs with a sufficiently-expressive type system properly used. That's why type systems have progressed from basic systems evolved from ones whose main purpose was laying out memory rather than correctness to more elaborate systems.
Many bugs of these classes can be avoided with a sufficiently expressive type system. There’s a reason that Haskell programmers say if it compiles, it probably works correctly.
With a sufficiently powerful type system (and typescript is basically the only non-functional language that makes the cut here) these aren't all that distinct. But even in codebases that don't take advantage of that power, this has not been my experience. I recently converted about ten thousand lines of legacy javascript to typescript at work, and discovered several hundred type errors in the process. State bugs also slip through pretty often, but we almost always catch pure business logic errors at code review.
What if you drastically reduce the possibility of inconsistent state by making it unrepresentable at the type level?
What if you immediately know that you’ve exhaustively handled all your cases?
What if types push parsing in the right direction?
If you test-call such a function without arguments you will then know what kinds of values you can expect it to return.
The argument default values can not be inner functions but they can be any function that is in scope. Or if you are using classes it could a reference to any method of 'this'.
Then add some asserts inside the function to express how the result relates to the arguments. No complicated higher-order type-definitions needed to basically make it clear what you can expect from a function. Add a comment to make it even clearer.
Only true if you're using very simple types, i.e. number and string. But "string" is pretty close to "any" and doesn't give you much info. If my function only expects two or three possible strings, it should be typed to only take those ones.
Comments are not a solution for much of anything, btw, and they only "work" if you read them. How many comments are in your node_modules folder?
There is no "dependency injection" in a functional world, take this opportunity to show your colleague how FP makes their life easier. It's just a function.
Instead of a class, implementing an interface, created by a factory, requiring a constructor, all you need is a function.
Anything that was previously a "dependency" in OO terms is now an argument to your function. If you want to "inject" that dependency you simply partially apply your function, the result is then of course a function with that "dependency" "injected" which can then be used as usual. In JavaScript there's even a nifty built-in prototype method on every function called `Function.prototype.bind` which allows you to do the partial application to create the "dependency injected" function!
Example:
```
const iRequireDependencies = (dependencyA, dependencyB, actualArgumentC, actualArgumentD, ...etc) => console.log(dependencyA, dependencyB, actualArgumentC, actualArgumentD, ...etc);
const withRandomDependencies = iRequireDependencies.bind(undefined, 'randomA', 'randomB')
withRandomDependencies('actualA', 'actualB', 'actualC', 'actualD', 'actualE') // etc
// => 'randomA' 'randomB' 'actualA' 'actualB' 'actualC' 'actualD' 'actualE'
```
Sure, there's solutions for this in the FP world, but in my experience they tend to have their own drawbacks. Admittedly, I've only ever used TS on the front-end (with no DI), so I've never really looked at what FP-style libraries exist for this.
My view on that is that it’s ok to use OOP and define classes if you are really defining an OOP style object. Back in the 90s is was taught that an object has identify, state and behaviour. So you you don’t have all three, it’s not really an object in the OOP style.
Looking at it through this lens helps make it clearer when you should add classes or just stick to function and closures.
Indeed, if you want to use emitDecoratorMetadata for automatic dependency injection, you should use classes. If the library itself takes advantage (again likely due to decorators) of classes e.g. https://typegraphql.com/docs/getting-started.html then yes, classes are again a fine choice.
The general answer is that they're useful when the type also needs to have a run-time representation (and metadata). Otherwise, not really.
A few objects contain state like say a DB connection/client or a RequestContext you pass down through your request handler middleware’s. Those are an OOP class with an interface definition.
Everything else is just functions and closures. We also generate interface objects from our GraphQL types but that’s not a real OOP type, it’s just an interface.
If you keep to that structure, you’ll largely avoid the whole polymorphism OOP type hierarchy hell and all the dangers that come with it.
As for DI (dependency injection), that’s honestly just a fancy form of passing parameters down through function calls. Technically, the RequestContext I mentioned before is a “ball of mud” provider pattern DI code smell. So maybe down the road we will use DI to create more constrained context scopes.
If I do go that route for DI, I would likely strongly follow a CQRS style class pattern to inject objects and keep them nicely named and organized. Would also fit nicely pattern wise with the existing function + closures architecture.
But yeah, overall, stick to functions and closures, use OOP style classes sparingly and you’ll get the best of all worlds.
If you got your first taste of typescript from angular and have a full stack background in c#/ Java class based style will make you feel right at home.
React seems to oscillate between the 2 styles.
My recent work in Svelte send to favor functions and types.
IMO the biggest benefit of classes is the code organization it brings. Have you ever seen a “util” class or folder. That’s what tends to happen to a code base without strong cohesion. It becomes hard to find anything.
With typescript/js you have modules as a pretty good substitute.
What I love about typescript is that you can mix the two.
Mostly classless module based with the occasional class (logger with a constructor to pass in the current module name for example) seems to be what I like most now. Use what makes sense.
It comes down to choice; pick one for the project and be consistent. That’s all that matters.
DI does not require OOP, and you don’t need DI to write good TS/JS code. DI is common in OOP, but if you aren’t using OOP you don’t need DI anyway.
Because DI is just “give me the dependencies I need when I declare I need it” you can use simple classes as scopes similar to CQRS patterns and continue doing functional programming from there.
It’s quite neat how you can interchange between the two and have it work rather nicely.
Technically, you could even do the same thing with closures and avoid OOP style classes all together even.
DI lives on, it just looks a little bit different than the constructor injection we’re used to seeing in OOP.
Though I would be wary of introducing patterns and paradigms that make sense in a different language when Typescript offers an ultimately simpler solution. Working against the grain helps nobody. Goes for both OOP and FP, really.
ES modules, functions, and well designed TS models get you 95% of the way.
0 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
The biggest argument is that my functional-ish code is always 3x shorter with the same features, though.
Also, even if you didn't want to mock that way, you can get dependency injection with functions just by taking a parameter for a dependency. If dependency injection is the only reason you have to use a class, you probably shouldn't use a class.
in Java it means "you should implement this contract"
in Typescript it means "this data type has this particular shape. It may have methods, too".
in Go it means "I'd like users of this code to implement these methods" (client interfaces).
In all cases you have to work differently with them. It's not even about OOP, I think, to the point where I'm not sure now if the keyword 'interface' is part of OOP at all.
It is a bit annoying sometimes that you can’t have overloaded functions with different types, but in that case you can usually just give the overloads different names, and usually that’s better for readability anyway. (Or if you really want to, write one function and use JS reflection to do the overloading manually) (but you really don’t!)
Here’s an interesting discussion of the overloading question in Swift: https://belkadan.com/blog/2021/08/Swift-Regret-Type-based-Ov...
it is not - dynamic reflection certainly has its issues, but static reflection is absolutely fine
> it’s good to be forced to do without it.
it just leads to people reinventing it even more badly with separate tools
I actually think this is a super cool and elegant way to do overloading
But I will check if maybe I can use DeepKit to auto-generate files with the reflection info I need as a separate build step.
Worse there is one value that is both a user-definable TypeScript type and a JS value.
How is being able to check code correctness at compile-time even close to "just linting and documentation basically"? This has to be a bad faith argument
> Worse there is one value that is both a user-definable TypeScript type and a JS value.
What does this even mean? I don't think you understand TypeScript.
You can do this:
function merge<
A extends Record<string, unknown>,
B extends Record<string, unknown>
>(a: A, b: B): A & B {
return { ...a, ...b }
}
const result = merge({ a: 1 }, { b: 2 }) const result = merge({ a: 1 }, { a: "fdsfsd" })
The correct type is quite complex and depends on whether or not `exactOptionalPropertyTypes` is enabled.EDIT: I think this is correct for when `exactOptionalPropertyTypes` is off.
type OptionalKeys<T extends { [key in symbol | string | number]?: unknown }> = { [K in keyof T]: {} extends Pick<T, K> ? K : never }[keyof T]
function merge<
A extends { [K in symbol | string | number]?: unknown },
B extends { [K in symbol | string | number]?: unknown },
>(a: A, b: B): {
[K in Exclude<keyof B, keyof A>]: B[K]
} & {
[K in Exclude<keyof A, keyof B>]: A[K]
} & {
[K in keyof A & keyof B]: K extends OptionalKeys<B> ? A[K] | Exclude<B[K], undefined> : B[K];
}
That's for when `exactOptionPropertyTypes` is enabled. With it disabled, then you'd replace `Exclude<B[K], undefined>` with `B[K]`.As to whether this is a good idea. Ah... it's not :P
Technically TypeScript "object types" only describe properties of some value. And in JavaScript... arrays have properties, and primitives have properties. Arrays even have a prototype object and can be indexed with string keys. So... {} doesn't actually mean "any object", it means "any value"
At its boundaries, TypeScript has blind-spots that can't realistically be made totally sound. So the best way to think of it is as a 90% solution to type-safety (which is still very helpful!)
It’s very opinionated about the way you structure your code and basically makes anything thats not fully fp-ts hard to integrate, and also is quite hard for general JS people to wrap their head around.
It’s been designed by FP people for FP people and if there are some on your team who are not fully on board or are just starting to learn FP - expect lots of friction.
At my company it was mostly scala coders and “cats” lovers (category theory stuff lib for scala) mixed in with regular nodejs devs and I could sense a lot of animosity around fp-ts and its use.
But on a more practical note, the more they converted their codebase to fp-ts the more they reported massive compile time slowness. Like it would start to take minutes to compile their relatively isolated and straight forward services.
From what I gathered, if you want to go fp-ts its just too much friction and you’re much better off picking up a language designed from the bottom up for that - scala / ocaml / elixr / etc.
To be honest once I’ve been comfortable enough with the more advanced TS features, you can write plain old javascript in a very functional style, and thats actually pretty great, especially if you throw date-fns, lodash/fp or ramda into the mix, and it remains largely approachable to people outside of FP and you can easily integrate external libs.
IMHO, functional TS is great with ramda, currying etc. and solves a lot of problems nicely. See also https://mostly-adequate.gitbook.io/mostly-adequate-guide/ch0...
Ramda et al seem like a good compromise. Looking through its docs though, doesn't JS have a lot of this stuff covered? ie filter, map, reduce etc. What new stuff is it bringing in that covers say the 90% of most use cases?
interface ClientMessage { content: string }
function isClientMessage(thing: unknown): thing is ClientMessage { return thing !== null && typeof thing === 'object' && typeof thing.content === 'string' }
expect(isClientMessage('nope')).toBeFalse()
expect(isClientMessage({ content: 'yup' })).toBeTrue()
but user-defined type guards basically duplicate the interface, are prone to error, and can be very verbose. io-ts solves this by creating a run-time schema from which build-time types can be inferred, giving you both an interface and an automatically generated type guard:
import { string, type } from 'io-ts'
const ClientMessage = type({ content: string })
expect(ClientMessage.is('nope')).toBeFalse()
expect(ClientMessage.is({ content: 'yup' })).toBeTrue()
Very nifty for my client/server monorepo using Yarn workspaces where the client and server message types are basically just a union of interfaces (of various complexity) defined in io-ts. Then I can just:
ws.on('message', msg => {
if (ClientMessage.is(msg)) {
// fullfill client's request
} else {
// handle invalid request
}
})Only thing missing is additional validation, which I think can be achieved with more complicated codec definitions in io-ts.
In practice though I find that they don't mesh well with the language and ecosystem at large. Using them in a React/Vue/Whatever app will catch you at every step, as neither the language nor the frameworks have these principles at their core. It takes a lot of effort to escape from their gravitational pull. Using Zod [2] for your parsing needs and strict TS settings for the rest feel more natural.
It could work in a framework-agnostic backend or logic-heavy codebase where the majority of the devs are in to FP and really want / have to use Typescript.
0 - https://gcanti.github.io/fp-ts/
1 - https://gcanti.github.io/io-ts/
2 - https://zod.dev
If you go all in and use every utility it provides to write super succint FP code, it can get pretty unreadable.
AFAIK Italy is the major outlier in having widespread sockets[2] that do not accept the Europlug
[0] https://en.wikipedia.org/wiki/Schuko
[1] https://en.wikipedia.org/wiki/Europlug
[2] https://en.wikipedia.org/wiki/AC_power_plugs_and_sockets#Ita... the one on the left, rated for 16A.
If the server is new and fresh, yeah it's ok to assume the payload of a request will be a JSON object with some required fields, but leave the parameter there in case someone decides they will start sending XML payloads to it
It seems that the author added a way to get notified by email, which might work for you: https://type-level-typescript.com/03-objects-and-records
Personally I set a crontab to alert me if the next chapter page's size increases above 50kB.
Quite often I get a function that's working correctly but typed incorrectly (including in someone else's typescript definitions), and sometimes I can correct them but other times I can't even read the original author's intent...
And edit: It's not that types have to be simple, but that complex types (especially) should be readable, as in you can follow the complexity step by step, line by line.
I feel like that definition is the TypeScript equivalent of "callback hell" or similar. It almost looks minified or obfuscated, or just written to be super terse instead of clear. I don't really know which it is, because I can't even begin to read it...
I'm not a TypeScript expert by any stretch, but I've been using it 40 hours a week for the last year and I SHOULD at least be able to start to read it... but nope. And I come across examples like that multiple times a week. It's just a really bizarre syntax, unlike any of the other languages I've ever used. I think it's like that because they had to hack it on top of Javascript, vs a language being strongly-typed from the getgo.
This is the source of most of typescript's flaws, but the mediocre type syntax was a deliberate choice: it's all erased at compile time, so javascript imposes no constraints. My guess is that it's just because many of the original typescript devs were on the C# language team.
The problem with typescript is that the types are very often wrong or needlessly complicated.
Having separate type definitions from the library is stupid. Having types missing from @types/node is stupid. Many libraries lie about their types.
You can act like typescript is some kind of magic miracle that will save you time, but in reality it is just riddled with many small time-consuming stupidities. Typescript is just trying to a polished turd. Shiny on the outside, but shit within, and this is by design because it needs to work with JS and other poorly typed libraries and code.
If I haven’t convinced you TS is a polished turd, just wait until you find out how to import a modern nodejs module in a typescript project. Hint: you have to import a .js file, even though your file is called .ts.
The tsconfig has so many options that every project is different and a lot of code isn’t interchangeable and can break if copied from one project to another.
Want to convert some TS code to JS without checking types? No can do! Ts-node can do it, but tsc cannot.
I'm not saying typescripts typesystem is perfect and I'm definitely not saying that most people use it correctly. But at least it has great potential, compared to e.g. Java and C# which still fail to let me describe basic data types and operations in the typesystem.
But even at its simplest variant, with just one or two functions passed, what the V0 or T1 do is pretty confusing. I thiiiiiiiiink it's trying to ensure the return type of one function is correctly passed as the input type of the subsequent function, and so on and so forth, but I don't really know.
Also, I should note that I'm using an older version of that lib. The latest version has cleaner typings... still difficult, but at least formatted better: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...
The latest version seems to have abandoned the typing of the initial input, which makes the types a little simpler.
My point however is that the types you point our here are actually not particularly complex. They are just long, with lot's of inputs for the generics (and the syntax may be confusing). The types in the original article are much more complicated, with conditional type inference etc.
The point being that people should write actual code, and should not write type code, simply because given an opportunity to write things, people will indeed write things, whether actually helpful or (more likely) not.
add(n: number)
add(s: string)
In TypeScript (and also in C and Objective-C) you need to give them different names:
addNumber(n: number)
addString(s: string)
But see also brundolf’s reply -- if you have a single function that happens to take multiple overloads, TS does let you declare each overload; but it still needs a single implementation (likely with some runtime dispatching) in that case. I haven’t used that much myself, but maybe I should give it a go!
You can sometimes loose the type though, so I prefer to do it with off the shelf filter/map/reduce even if its a bit unsightly.
I personally reach for lodash/fp for more specific expressions like orderBy or groupBy. There are some very nice well documented and powerful primitives there.
At one team a guy got so enamored with the functional style that he went ahead and rewrote mountains of logic in lodash/fp. And it turned out quite hard to maintain by the rest of the team, so you also have the danger of “overdoing it for the rest of the team” danger as well.
My concern is with fiddly runtime stuff like “instanceof” -- I find that can go wrong in surprising ways. Better to just trust values to implement the interface they say they implement rather than trying to forcibly cast them.
type Value = { a: string }
const result = merge<Value, Value>({ a: 1 }, { a: "fdsfsd" }) Avoid the Object and {} types, as they mean 'any non-nullish value'.
This is a point of confusion for many developers, who think it means 'any object type'.
https://github.com/typescript-eslint/typescript-eslint/blob/...See:
- https://typescript-eslint.io/rules/ban-types/
- https://github.com/typescript-eslint/typescript-eslint/issue...
function merge<
A extends object,
B extends object
>(a: A, b: B): A & B {
return { ...a, ...b }
}
const result = merge(() => {}, () => {}) // should fail!
const anotherResult = merge([1, 2], [3, 4]) // should fail!
Which is obviously not what you want.This table here gives you a good overview of differences between object and Record<string, unknown>: https://www.reddit.com/r/typescript/comments/tq3m4f/the_diff...
For example, an SQL query builder library may internally do unchecked assertions about the type of the result row that a query transformation would produce (e.g. group_by), however assuming that part is correct, all application code using the query builder's group_by method would benefit from the correct row types being produced by `group_by` which can then be matched against the rest of the application code.
But I think it's more nuanced. Depends of what you mean by type correct. Even in Haskell you can override the compiler and say "trust me on this".
TypeScript could restrict this to be through an operation that creates a new array (e.g.: via .slice()), however that would impose a performance deficit versus vanilla JavaScript. It's not acceptable to the TypeScript project to impose a cost on what would, in JS, just be array assignment or argument passing.
It would be a neat idea to allow a "strictCovariance" mode to allow covariance only with readonly arrays - I think that might solve the issue? i.e.: I can cast "Cat[]" to a "readonly Animal[]", but not to a mutable "Animal[]".
Complex to understand code does not entail high quality of maintainers. Quite the opposite in my experience.
Writing simple types with Typescript is simple, and improves any Javascript code.
Writing complex types (inference, generics, inheritance) is really punishing. And people that don’t truly know what they’re doing won’t even try (or at least in my experience, I’ve never seen them try).
let sort (iterator: 'b -> 'a list) (collector: 'a list -> 'c) (comparator: 'a -> 'a -> bool) (collection: 'b) -> 'c =
...
I added the type annotations to, hopefully make it clear. The iterator is a helper to convert some arbitrary collection to a list, with the collector turning it back into a collection type again (not necessarily the same.)For example, the iterator could map from a tree to a list, and the collector then to an array. Or if you already have a list and want a list back, you could pass in the identity function for those.
One call could be the following:
sort id id (>) somelist
Hope it isn't too unreadable.Edit: Adjusted the order of the arguments, as the original order wouldn't work too well with partial application.
What are Unix man-pages but comments about the APIs they describe? Are you saying you would prefer to replace them with the TypeScript type-language?
As I see it TypeScript is a a solution to the problem of how to describe a function, what it does, what it expects from its arguments and what it returns. That information can often be clearly and simply expressed with a comment, rather than with a complicated type-declaration. And type-language only describes the syntactic behavior of a module, not its semantics.
When type-declarations become more complicated than the code they are describing I think we're at a point of diminishing returns.
The other purpose of type-declarations is to catch errors. But if a declaration is very complicated how can we be sure there's no errors in it?
Documentation. Obviously not the same thing as inline comments in the code. You can generate documentation using comments (i.e. jsdoc), but comments are the weakest form of guidance for other developers. Types don't replace documentation, but are part of the same goal: making code easier to consume.
> When type-declarations become more complicated than the code they are describing
Do you find this happening to you often? JavaScript is a very permissive language, and most JS devs learn to write code in a way that is difficult to type. That's a part of the learning curve of the language. Part of using TS well, is realizing that complicated types are a smell for complicated behavior, and modeling your data in a conceptually simple way.
It's not just about labeling everything string or number, but making it impossible to use the code the wrong way.
> But if a declaration is very complicated how can we be sure there's no errors in it?
Simple. You test them. Same as any other code. How do you test your comments?
So comments are a solution to something. Don't TypeScript programmers use them as well?
I agree that TypeScript, in and of itself, doesn't change the way the code executes. This is self-evident.
But the idea that that makes it simply "fancy documentation" is hilarious. I have never seen documentation that can tell you at compile-time how your code will behave, it's fundamentally stupid to argue that static type checking is in any way comparable with documentation
Its rarely seen in the ecosystem as a solution, unfortunately (everyone is passing all arguments all the time), but its one of the rare places where this is still useful. I've had bad experience with the alternative (continuation local storage) and its not nearly as elegant.
I totally agree there are other good/better options (as evidenced by react and many others), but I don't think this is an entirely fair comparison. The main "DI" alternatives in React are hooks, context, and imports, none of which could really replace traditional IoC containers on the backend without some modifications.
I think the main thing that makes automatic DI easy with OOP is the clear separation between dependencies (constructor parameters), and method parameters. Admittedly, this is totally possible with FP, but requires some good conventions and doesn't seem to be nearly as popular.
I also think most OOP langs have terrible syntax for constructors, which makes it look clunkier than it really is. Primary constructors (e.g. kotlin) make this not much more verbose than the FP alternative.
> Angular 1.5 dep system
Yes it's vanilla JS, but I doubt there's many FP people that would call that functional. The examples I saw are all just using JS functions as "discount classes", which definitely aren't pure or functional.
A big bulk of their compile time came from things that could've been checked in much more efficient (for the compiler) ways.
I would never urge anyone to not use statically typed languages, mind you, I think people just need to be a bit more pragmatic. Sometimes I find it unfortunate that TypeScript provides the facilities it does while still not having solved the basic ergonomics of types (more consistent inference, etc.). Having types that generate types creates problems that I honestly don't experience anywhere else and I would rather that people just not in general, but that's something you can fix with rules.
An ironic part of all of this is that Haskell's type system is a lot easier to use and use well than TypeScript's, in the end, which is especially funny considering all the talk of pragmatism.
export const myFn = (...deps) =>
You don't need anything else, for encapsulation you have the EsModule, that what OP meant.
Another random example from Axios: <T = V>(onFulfilled?: (value: V) => T | Promise<T>, onRejected?: (error: any) => any): number;
Or eslint: type Prepend<Tuple extends any[], Addend> = ((_: Addend, ..._1: Tuple) => any) extends (..._: infer Result) => any ? Result : never;
Here's another real example from today... I was trying to figure out how to type "the name of this type's key has to be one of the following strings in this enum, but the type doesn't need to have all the keys". Here's a Stack link with the right answer: https://stackoverflow.com/a/59213781, but it wasn't easy to figure out. At first I thought it would be `[key in Partial<MyEnum>]`, but nope. Maybe optional? `[key in MyEnum]?` kind of works but fails in an new way (see the Stack for details). The correct way to do it is apparently `Partial<Record<MyEnum, unknown>>`, which I NEVER would have been able to figure out. Why the record? Why the unknown? Who knows..?
Don't get me wrong, I love TypeScript for the simpler use cases, and a lot of it IS that, thankfully. But the more complex compositions, especially in popular third-party libs? I've given up lol.
The use of single-letter keywords (K, T, V, P, R, etc.) combined with confusing re-use of punctuation (<> and : and () and []) that mean subtly different things depending on where they're used, on top of how JS already uses them, makes it even more so. Sometimes I wish TypeScript were more verbose and opted for longer, clearer constructs rather than stacked shorthands...
It returns a function. The one that's equivalent to applying the arguments in reverse order. I think that this signature is pretty clear for anyone experienced with a statically typed language with generics and higher order functions.
On the other hand, I have no idea why a compose function that takes exactly 6 arguments, the last of which is function which takes 3 arguments would be a desirable abstraction. But I don't think static typing is necessarily to blame for this -- this just looks like a clunky function that has a clunky type.
I'm fully with you on the second example though.
Sounds like I have stuff to study!
Maybe ramda was an extreme example (with or without typescript, it was so hard to read that our dev team decided to just remove it altogether and replace it with more verbose but easier to read vanillaJS code or equivalent lodash functions). But I come across difficult TypeScript examples nearly every day of my work, where I feel like I'm reading an obfuscated leetcode challenge instead of the straightforward business logic in the rest of the codebase.
Once I finally understand a complex type, my usual reaction is, "That's it? That's all that was trying to express?" It's just an arcane syntax to me. Sounds like learning about generics and higher-order functions in statically typed languages would be a good starting place... thanks!
Libraries exist, in part, to encapsulate high complexity.
There's likely accompanying documentation for the examples you provided.
In some other languages you have similar stuff, with the added complexity of concurrency and memory management related types. If you are struggling with TypeScript, let me tell you about a whole new world of pain called C++.
That's why I think every programmer should learn C++.
Say no to single-letter variables!
It also must have documentation other than the type annotation.
Just look at some of other examples in the sibling comments!
[0] https://www.amazon.pl/Konstructor-Oryginalny-zestaw-konstruk...
> except the polarity isn't fixed, that's its one theoretical disadvantage
Polarity isn’t fixed on any mains sockets. That’s why the A in AC stands for “alternating”
I suggest to try to get your boss to sponsor this, since you need it for the job. It will also make your dev experience so much more fun!
Learning is great, but this job, like many others, doesn't really have a well-defined system for training, documentation, professional development, or anything like that. Either I learn on the job as it happens or I don't learn at all. There's always too much to build, with constantly shifting priorities defined and redefined by higher-ups who don't know or care what TypeScript is. Sure, I can push back on that to some degree and beg for a resource or two, but even that is difficult, and there's always so much else that's even more pressing to learn. And web dev by its nature is a broad and shallow career anyway, especially on the frontend... by the time you begin to master something, it's already obsolete lol.
TypeScript looks like it'll have some staying power, so I'm happy to learn it as I go. But over time, I've learned to stop chasing perfection and to just go for "Will this survive long enough until the rewrite in a year or two? If so, good enough...". I've never known a job like this where code survives longer than 2 or 3 years before someone, either a dev or a manager, wants to rewrite it from scratch.
A lot of our existing codebase was written by contractors who had a lot of experience, but little desire to document anything or comment anywhere. Our current generation of (relatively junior) devs inherited that, has trouble with a lot of it, and ends up rewriting large overengineered swaths in simpler patterns as we go. A complete rewrite is already planned. And so the cycle continues :)
In general, the barrier to entry for JS/web dev is pretty low, and so there are a lot of low-to-med skilled devs like me in the industry. I think, philosophically, I lean against writing code that is overly "clever" rather than readable. Similarly with types. If a typing becomes complex enough that it's not really readable, I'd rather just leave a clear comment as to what the intent is and then move on, knowing that the code itself -- much less its typing -- is unlikely to survive long anyway. At the end of the day, IMO, it doesn't make sense to have types that are more complicated than the code itself... if correctness is important but the typing is complicated, I try to break down the code itself, add comments, add unit tests, add documentation, etc. rather than try to coerce TypeScript into sentience.
Is that the most "correct" way? Probably not. But it sure makes things easier to read in PRs rather than telling everyone, "Well, you need to learn advanced TypeScript if you want to read my contribs."
But typescripts advanced features are more like advanced SQL. Sure, you'll learn some chunk specific to your database, but the majority will be transferable. And just like SQL it won't become outdated knowledge in the next decades most likely.
So I still think it will be worth. If not for the company then at least for yourself. :)
However I always understood potential to be different to polarity. And that AC (which, to my knowledge, all electric grids globally carry) is the literal oscillation of polarity. What am I missing/misunderstanding from the GPs post?
But that’s what I meant, the UK and US plugs (as well as switzerland I think?) theoretically have one pin always be hot, one always be neutral.
With Schuko, you can reverse the plug, and it'll still work, which is on the one hand awesome when you've got a tight space and want more plugs to fit, but can also require higher costs, as you've always got to switch both wires instead of just switching the hot one (although this is best practices everywhere, as you can never know how well the electrician followed specs when wiring your apartment 90 years ago).
I'm only a casual user, so take this with more than a grain of salt, but for me typescript occupies quite a weird point in statically typed language space: on the other hand, typescript's type system is enormously complex (and also quite expressive). Part, but I don't think all of this, comes from being retrofitted onto a untyped language and its ecosystem (so e.g. sum-types tend to be implicit rather than tagged, and in addition to discriminated unsions, there is support for complex sub-typing from the OO heritage). Most statically typed languages have no direct equivalent for many of typescript's more advanced features (e.g. partial types, although Scala and Ocaml have related constructs, in Ocaml's case e.g. polymorphic variants).
But on the other hand it's surprisingly awkward to get what I would consider one of the most basic and beneficial features of a sane statically typed language, namely exhaustiveness checks -- so most people don't even bother. In fact there is not even an agreed upon idiom (just google "exhaustiveness check typescript", all the answers will look spuriously different). The basic pattern is that you want a helper function like so:
function assertUnreachable(_value: never): never {
throw new Error("Statement should be unreachable");
}
and then for any switch(foo) you do an default: assertUnreachable(foo). I can't really fathom why there isn't a better way to express this (the ability is clearly there, but it's un-ergonomic). But if you want something that transfers well, I'd probably de-emphasize the fancy stuff typescript offers unless needed for acceptable JS interop and concentrate more on thinking about exhaustiveness and making undesirable states unrepresentable.Therefore I think the chance is high that even if there are currently not too many languages with advanced features (generics are not advanced btw), we will see more and more of them in the future.
Half the breakers are connected to one hot, and half are connected to the other hot. For 120V you wire hot/neutral to the receptacle, while for 240V you wire hot/hot. (Plus ground, of course.)
In the EU, receptacles are wired hot/hot, and there is no neutral conductor.
The typical EU configuration:
The transformer delivers a neutral and three phases, in a star configuration.
That means you've got L1, L2, L3, N and GND.
N to any L is 230V, any L to any other L is 380V.
That also means a typical grounded socket has e.g., L1, N and GND, so a neutral and a hot.
A high-power socket or e.g. a stove will have GND, N, L1, L2, L3.
My stove has the oven running on L1 and N in a 230V hot/neutral and the stove at L2 and L3 in a 400V hot/hot configuration.
(Belgium is the exception, having phases at 113V off the center point, so sockets are in hot/hot to get 230V between the phases)
Here in the US, where split-phase is the residential standard, a house with three phase is quite rare. The HV lines running on poles in a neighborhood are mostly single phase, at least in rural areas like mine.
Yes! And many electrical devices rely on it, though sometimes fallback to regular 230V single-phase at 32A is possible, e.g. for stoves.
And considering a typical stove runs at 11-15kW and a typical electric water heater between 15kW to 25kW, you'll need it as otherwise you'll need far higher amps than is reasonable.
Honestly, only due to the technology connections video did I realize that the US does not use triphase power in most homes, which was genuinely surprising.
I have a friend with a Bridgeport vertical mill in his garage/workshop. He had to build a single phase to three phase converter, so he could run its three phase motors.