255 comments

[ 3.4 ms ] story [ 269 ms ] thread
I'm always impressed at how much the type system in TS is capable of. The provided examples remind me of what I'd expect in something like Rust; it brings me joy that we can do this sort of stuff in our frontend code and tooling these days.

We have come far.

I'm currently working on a project that uses both Typescript and Scala. The overlap in concepts is rather useful when switching contexts.
I particularly love the interactivity of the training. So polished.

Can't get enough of the fireworks!

This is fantastic! I've often had to advise coworkers to read documentation for OCaml or Rust to learn idiomatic, functional, statically typed programming. It's great to see a Typescript specific resource with exercises.
Wow I think I know a lot of Typescript but I'll have to go through it because I'm always asked for ressources to get started and this one seem great.

I also recommend type-challenges: https://github.com/type-challenges/type-challenges

It works great with the VSCode extension.

Seems like a good start, but there are a lot of interesting offerings in the introduction that really don't exist in the content so far.

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.

This looks fantastic, not just for people learning Typescript, but I'd think it would be useful (when completed) as an introduction to generics and type-level thinking etc for lots of newcomers to those areas.
There are only 3 chapters so far...
I love the content!!
Great content. Give it me to me as a daily-dose newsletter :)
Looks cool. I like how it gives immidiate feedback, but doesn't feel constraining or arbitrary. Is there are more basic tutorial in a similar style?
my HTML blog only has 30 lines of JS. i didn't need a framework and i certainly don't need types. grumble grumble web should just be simple html javascript grumble serverside render in my pet language grumble /s
Add another 1 to your army. We all know how Typelevel Scala ended.
I wish this issue would get addressed: https://github.com/microsoft/vscode/issues/94679

Showing fully resolved types in Intellisense would be the single largest usability enhancement they could make for me right now..

The Id<T> type defined in the first comment seems like a pretty good, albeit ideally unnecessary, workaround.
Those hacks work but in practice I wouldn't classify them as "good". You end up having to look at a ton of types including in library code like React and etc that can be quite complex. Having to stop and try to wrap those on the fly is terrible ergonomics.
You might also be able to leverage typeof in conjunction with Id<T>. Like if you have some parameter x with a complex type you can create a temporary variable of type Id<typeof x> to avoid looking up any additional types. Totally agree it should be supported out of the box, however.
Yeah, this is a recurring pain.

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.

Try any IntelliJ IDE, they’ve got that. I can’t understand why anyone would want to use VS Code with a Typescript project voluntarily…
Nice course, and nice site.

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

I don't believe it is using any type system described outside of the TypeScript compiler. The goal of the system is to accurately describe real-world JavaScript programs and semantics, not to introduce a formalization that existed elsewhere and impose it on JS.

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

Non-soundness is sort of a feature, it lets you force your way through and just say "trust me, this is a Thing" when it's just hard (or impossible) to make TypeScript see that. In practice, you can write large code bases where you only need to do this every 1000 lines or so. Not ideal, but better than no typing.
Is it fair to say that the limitation is that the type checker can admit programs that are not type correct?
Yes. For example it has the "any" type which will bypass any sort of type check.

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

Any isn’t required. The go-to example of unsoundness is the Cat[] ref that you alias as an Animal[], append a Dog to, then map over the original ref calling ‘meow()’ on each entry.
JavaScript arrays are heterogeneous and TypeScript permits covariance here because the underlying language lacks any mechanism to freeze any shared references to the array to prevent it.

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[]".

Yea, because typescript is trying to model what can be done in JavaScript, which is a very permisssive runtime.
That's by design, considering that its original purpose was to introduce static typing in JS codebases.
You can make "believe_me" assertions, which is incredibly useful when writing advanced (metaprogramming heavy) library code. The idea is to try and contain / and heavily test the small "unsafe" library part and isolate it from the rest of the code, then enjoy the advanced type transformations and checks in the "normal" application code.

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.

Being able to opt in to unsoundness is arguably a feature, but TypeScript is still unsound even without using assertions or `any`.
Just came here to say that this is really nice. Fantastic way to play with Typescript Types even for people with decent knowledge of Typescript as I would consider myself.

Particularly enjoy the confetti :)

Speaking of types, what are your thoughts on fp-ts if you've used it? It brings functional programming concepts like in Haskell such as monads into TypeScript.
haven’t used it myself but other teams at the company I work for have tried with mixed results.

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.

That sounds about what I've expected. Frankly in a TS codebase with many other devs that are not versed in FP, I wouldn't want to bring in a pure FP library because it, like you said, needs everyone to understand the "meta-language" of FP so to speak, such as how monads work, not having raw side effects, mutation etc.

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?

Well currying is a big one, and also functions like flow/pipe (in lodash/fp) mimic piping in FP languages which allows for very nice expressions of business logic for modifying data.

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.

fp-ts [0] and the accompanying io-ts [1] are very well designed and neatly implemented. I'd consider them the reference for all things FP in Typescript.

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

I haven't used fp-ts directly, but I use an adjacent package that declares fp-ts as a peer dependency: io-ts. I've almost exclusively for easier type management during deserialization. In vanilla TypeScript I would have defined an interface and a user-defined type guard to handle deserialization:

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.

If you stick to a few useful types like `Option` and `Either`/`TaskEither` you can get a lot of value out of it when writing server side code, particularly when combined with `io-ts` for safely parsing data.

If you go all in and use every utility it provides to write super succint FP code, it can get pretty unreadable.

A great idea. Now, everyone that learns this stuff, show some restraint!

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.

The types in your code are just as designed just like any other aspect of it. It's not a matter of restraint, it's a matter of doing things on the correct way.
So less can be more but more can also be more, more or less?
Exactly. "More" and "less" are pretty much wrong ways to measure it.
I don't believe in "correct" when it comes to software dev. There's 1000 solutions to any given problem.

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.

(comment deleted)
I definitely know what you mean. There is usually a type that is "just right" in terms of rigidity and flexibility.

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.

What's 'correct' from your point of view (as library implementer) may be 'overly complex and a hassle to use' from the library user's pov though.
Second this. By the end of a progressive multi-year TS migration at my last company, we were refactoring `HTTPRequest<GetRequestBody<IncrementUpdate>>` back into its JS ancestor `HTTPGetRequest`.
True that.

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 would the code really work without the type checking?
Yes. I had a huge testing suite on the project and all tests were green even though there were tons of type errors.
You maybe just lack experience with more complex types. Which is totally expected, since you probably learned programming the runtime all the time, but not the typesystem - unless you come from one of those rare languages that have a similar powerful typesystem.

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’ve used C++, C# and Java. Do these count as languages with complex types? I also used typescript a lot and have plenty of more complex types using generics and dynamic generation of types based on other types.

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.

None of these count as a language with complex types in my opinion. C++ has templates but that's not to be confused with its typesystem.

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.

This is so true. I've been thinking recently that in the same way that "use boring technology" is a pretty well-known concept, so should "use boring types" enter the collective conscious. Type operators are exciting and flashy, but I found that using them too much leads to brittle and confusing types. Saving them for a last resort tends to be the right strategy. Often there's an extremely dumb way to write your types that works just as well - maybe even better :)
Agreed! Boring interface definitions is my rule for typing.

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.

Although to be fair, lack of visibility in the tooltip seems like a problem with the tools not with types. Many times I’ve wished I could tell TS to expand the next level of type signature.
If you only use "simple types" then you often get implicit assumptions about the "values". For instance note that "web-services" basically means you have a very simple interface defined by the HTTP-protocol.

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.

For that you have the "closed for modification but open for extension" principle.

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

No need to show any restraint. It's much more fun to explore and burn yourself once into understanding how much of this power your need. Or twice. Or as many times until it gets more fun to be pragmatic.
It's more fun, but your fun shouldn't come in the way of getting work done - or worse, getting in the way of others getting their work done.
I wouldn't say it's a matter of fun but often a matter of necessity in modern software development. Less experienced people can often have inflated egos and will refuse to listen to any advice from anyone else. Letting them fuck something up themselves (not too badly) after you've explained why it's a bad idea can be a hard but good lesson.
Ah, the "smoke a whole pack of cigarettes" method of teaching software dev.

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.

Also typescript doesn't always infer things thoroughly with generics and deeply nested types, so I’ve ended up avoiding them when possible. Performance also explodes if you have too much unions and stuff. Beware! Definitely some popular libraries out there that went overboard and nuke compiler performance for minimal gain.
My coding philosophy is centered around simple interfaces. I think of power sockets and plugs. The simpler the socket/plug design, the easier it is to plug in. It's easier to connect a European plug which has 2 round pins than it is to connect a UK plug which has 3 rectangular pins at different angles. You can imagine how difficult it would be to connect a plug with 10 pins; it would be difficult to get the alignment right and you would have to push hard and fiddle quite a bit to get it all the way in.

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.

I love the socket/plug analogy.

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.

A tiny bit more complex: the German/European plug has the ground at the sides and so goes in both ways. The French/Belgian plug has instead a ground pin that goes from the socket into the plug (so the opposite of the other two). That means generally you'll be able to combine both and most products have a plug that allows to connect to both varieties. But there are some (especially Chinese and American) products which miss this diversity and either only fit in one of the two or produce local varieties for either one when it would well be feasible to produce a single version that fits both.
The EU has a huge variety of plugs and sockets, what people often call the EU plug are variation of the schuko[0] design. The standard EU plug is the Europlug[1] which is compatible with most (but not all) sockets in Europe and only handles low amperage.

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.

> It's easier to connect a European plug which has 2 round pins than it is to connect a UK plug which has 3 rectangular pins at different angles.

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.

[0] https://www.youtube.com/watch?v=UEfP1OKKz_Q

The thing with electrical plus is that they should be designed around safety first, rather than convenience. And the U.K. plug is a lot more safety focused than many other plug standards.

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.

I don't think the discussion really was about plugs.
Originally, no. But discussions evolved
When I was a kid before we had legos we had some soviet alternative called constructor or something. Everything was made from metal, crews etc. obviously as a kid one of the first “hello world” things you will build is “plug” that you can insert into those holes in the wall. My older brother was lucky when he did it as the fuse in the house went off. My younger brother did the same about a decade later but holding in his hand two metal pins. He was also lucky that my dad was just passing through corridor and pushed him out. He got away with just burned skin on the fingers and a bit of shock.
And now try the Schuko, which improves on all metrics you named (except the polarity isn't fixed, that's its one theoretical disadvantage), but also significantly improves usability (you can plug it in either way, the plug goes in much easier, and it stays in with much more force)
It’s a good design but I disagree that it improves on all the safety features. For starts the child proof safety shutters are still only optional in some regions.

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

And yet there is a big difference between the hot and neutral conductors in a 120V outlet in the US. The neutral remains close to ground potential, and is actually bonded to earth at the breaker panel.
(comment deleted)
I’ll admit that my understanding of these things is rather superficial. I might understand more than the average person but that’s not exactly a high bar to set.

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?

English isn’t my native language, so I mixed up those two terms.

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

Ahh I see. Thanks for the clarification
The transformer on the pole delivers 240V rms center tapped, with the center tap being connected to ground at the pole. The center tap wire is actually uninsulated, and the two hots are wrapped around it for support. The center tap is connected to the neutral bus at the breaker panel.

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.

What? That's wrong.

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)

Thanks for the EU detail! Are you saying the typical house in the EU has three phase power delivered to it? All three 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.

> Thanks for the EU detail! Are you saying the typical house in the EU has three phase power delivered to it? All three phases?

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.

Although I use a propane range, I'm wired for a 240V 30A electric one. That's only 7.2KW. My water heater is also propane.

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.

It's true that EU plugs of some devices can be a bit loose; especially 2-pin variants where the wall socket is not indented or the indent is taller than the plug.
USB-C would be a nice looking single-argument function... but parameterized with every possible generic template meta-programming feature in the language.
Over the years I've actually found duck typed code bases to end up, for lack of a better word, less of a mess than those based on typed languages.

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.

If your `Foo` object has 60 potential character definitions... that sounds (smells) like it would be a code smell of something wrong upstream. Perhaps it's time to refactor the function.

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.

Less is more is also important for writing performant code. JS engines care about types, in the form of ‘shapes’ which is the V8 term for a specific structural layout and maps pretty neatly to TS structural types for obvious reasons. Simple types make performance issues like megamorphic inline cache issues much harder to create. If you see type spaghetti it’s a good hint that performance issues may be lurking depending on actual runtime usage. And if your runtime usage is actually simple then you don’t particularly need the flexibility the type spaghetti provides.
"Duplication is better than the wrong abstraction"

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.

The focus should be readability of code while respecting abstractions and design patterns.

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

I just finished writing a mess of very complicated types that can parse an open API spec, provide the request types (body & params) as input, and restrict the return to the appropriate response type... I hooked this higher order function into our API and immediately found 30-40 different places where the implementation was not aligned with the spec.

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

This is great. Can you please create an email list where we can sign up to be notified when new chapters are available? If I follow you on Twitter, I will invariably miss any announcements.
One think I struggled a lot with until I got it is that the TypeScript types and the JavaScript code live in totally separate universes, and you cannot cross from the type world to JavaScript values because the types are erased when transpilling - meaning they can't leave any trace.

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

Does TypeScript support dynamic dispatch? JS by nature is dynamically typed you should be able to introspect at get the type at runtime.
You might be interested in DeepKit[0]. In short, it enables introspection/reflection of typescript types at runtime, and builds off of that to do super interesting things like an ORM, an API framework, ... etc.

[0] https://deepkit.io/

Thanks, their @deepkit/type is exactly what I would need, but it seems they do that by a TypeScript plugin, and I'm in an esbuild setup which completely bypasses TypeScript.

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.

I think that’s actually a positive feature of TypeScript -- a useful limitation. Reflection is generally a bad idea and it’s good to be forced to do without it.

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

Wanted to note that you can do function signature overloading in typescript- you just have to have a single function at the bottom that encapsulates all the different signatures and then branches its logic dynamically based on the values it's given: https://stackoverflow.com/questions/13212625/typescript-func...

I actually think this is a super cool and elegant way to do overloading

> Reflection is generally a bad idea

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

You’re right, I was just thinking about dynamic reflection.

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.

How do you give the overloads different names? Can you give a little example?
I’m just thinking of separate functions like this, which you can do in many languages:

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!

That boundary is why I have a hard time taking TypeScript seriously. A type system that doesn't participate in code generation is what.. just for linting and documentation basically? Is that what we are become? Is that all people think a type system is good for?

Worse there is one value that is both a user-definable TypeScript type and a JS value.

> That boundary is why I have a hard time taking TypeScript seriously. A type system that doesn't participate in code generation is what

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.

It's just fancy documentation. It doesn't contribute to behavior at all. Typescript can describe polymorphism, lamely, but it can't drive it.
> It doesn't contribute to behavior at all

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

(comment deleted)
Is there any place I could report issues or ask questions about the course other than Twitter?

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.

> And I don't know how to express the correct solution (i.e. where we actually assert that A and B are object types).

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 })
Why Record and not object?
Because you only want to merge two objects that have keys with string type. "object" is represented as Record<any, any>. That would mean, you can use any type as key. Here is an example:

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

This fails on:

  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

Wow yeah it gets way too complex if you want to track the types of properties within the objects too! If that is the case, then I would just prefer to do this instead as it is much simpler:

  type Value = { a: string }
  const result = merge<Value, Value>({ a: 1 }, { a: "fdsfsd" })
Yeah, TypeScript gets funky around the boundary of "things that can only be objects", because JavaScript itself gets funky around "what is an object"

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!)

Hi n I have a question. I will go as simple and short as possible. I joined a small team working on the internal invoicing tool. Backend is Spring. Front-end is ExtJS used for me in very peculiar way. It emulates Java classes, there are Ext.define declarations with FQN names eg: "com.projectName.ds.Board.ui.extjs" (as string, casing important) Then in the code this class is instantiated by its FQN but used as identifier eg: var Board = new com.projectName.ds.Board.ui.extjs(); There are also a lot of FQNs with short namespaces, different are associated with business short names and other like Dc, Ds, Frame belong to code architecture domain (data controller, data store, a frame on the screen). How I could use typescript to improve developer experience here? I'm from the react world, I programmed 4 years only in typescript, react, node and mongo. Thanks!
Might as well ask here. On our teams, we have the occasional developer that is insistent on using Typescript in an OO fashion. This has always struck me as square peg round hole. Even though I come from an OO background, Typescript strict settings really seem to push me in a direction of using interfaces and types for type signatures, and almost never classes, subclasses, instantiated objects. I don't have a very good answer for "yeah, but what about dependency injection"? though. Any thoughts from anyone?
An ES module is encapsulated enough. You can dependency inject with them as you wish. It's like having a class, no need for a class inside a class.

The biggest argument is that my functional-ish code is always 3x shorter with the same features, though.

This. Creating classes to wrap dependencies is a pattern only needed because of language limitations. With JS/TS, you can mock at the import statement level, so no need to twist your code to abstract away importing.

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.

Request-scoped DI (as seen in ASP.NET MVC) is great on the backend for servicing requests. You can ask for e.g. a class representing the current user information to be injected anywhere, or to keep track of request-associated state like opentelemetry spans, or a transaction, etc. The alternative is to pass the user information class or transaction to all other services, which can be annoying

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.

The IoC is a nice paradigm that I tried using with a couple different libraries in JS, but they all felt like they had missing features and sometimes were annoying to run tests with (depending on the how they implemented the IoC container within their frameworks). The work Microsoft puts into their web frameworks to make them so cohesive with the rest of their ecosystem libraries is sometimes underrated
Yeah, the designs of DI libraries in TS land often leave me wondering what the author was thinking.
How do you dependency inject a ES module?
You'll export a function from your esModule if you want to inject

export const myFn = (...deps) =>

You don't need anything else, for encapsulation you have the EsModule, that what OP meant.

ah, thanks, I thought it was something different.
You have dynamic imports too if you need IoC.
I get this question sometimes from a developer new to my team asking if it’s ok to add OOP code since most of the existing code is just functions.

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.

I'll give a practical, non-philosophical answer.

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.

>I don't have a very good answer for "yeah, but what about dependency injection"? though. Any thoughts from anyone?

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'

```

The problem with that is that eventually you want to have the dependencies automatically injected (like how an IoC container would be used in a typical OOP application).

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.

Are you seeing any decorator or OOP style in React? Yet plenty of dependencies are automatically injected without the OOP jargon, if you want to see a pure JS example of auto DI go check angular 1.5 dep system, and Vuejs.
> React

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.

Why do you need automatic injection? What the parent said is idiomatic FP
Because in typical back-end software, the dependency tree can get very big very fast. Having an IoC container means devs only have to declare direct dependencies for each service, rather than constructing the entire tree (and figure out the necessary ordering, etc).
You could model that with one function taking that dependency and returning a new one with it included. Then you just use the one that has it included instead of the one that doesn't.
You still have to construct and pass in that dependency though, along with all of its dependencies, recursively.
I think the problem with DI is less about how they are passed in and more about how usually there is are only two implementations: the real one and a test one. The test one is a mock/stub based on assumed behaviours of the real thing. Obviously, such an approach is essential in some situations but in general it is mostly bad.
Interfaces are late bound, which, in conjunction with the concept of object identity and state, is OO. Subclassing is merely one specific approach to code reuse within the OO paradigm.
OO programming is a very versatile paradigm, and not at all incompatible with JS/TS.

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.

I successfully used functional programming with DI and it was quite pleasant!

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.

Do you mean something like this: f(arg1, arg2, arg3, deps)?
As I am not familiar with TS, I am gonna use F# as an example:

    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.

I keep our stack mostly functional and that’s how it was done before me on our current project.

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.

Either is valid.

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.

We use Classes extensively in our code because it is an Electron app that interfaces with hardware. OO Typescript is incredibly useful, as we have clearly defined objects and inheritance schemes that would be a nightmare in native JS. The syntactic sugar of TS classes delivers an enormously powerful verification system.
I think the word "interface" means something conceptually different in different languages.

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.

Some native JS constructs are class-based (or constructed using the new keyword). Promises, for example [0]. Nothing wrong the odd class here and there.

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

I like classes but I'm in the minority. They are just syntactic sugar over functions, but I like the explicitness of them. If a closure works for you, that's great but there isn't an objective reason to use one over the other.