top | item 32913125

Learn how to unleash the full potential of the type system of TypeScript

589 points| NeekGerd | 3 years ago |type-level-typescript.com

248 comments

order

cogman10|3 years ago

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.

jongjong|3 years ago

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.

johnfn|3 years ago

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

Cthulhu_|3 years ago

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

primitivesuave|3 years ago

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

tobyhinloopen|3 years ago

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

antipurist|3 years ago

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.

marcosdumay|3 years ago

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.

janee|3 years ago

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.

meheleventyone|3 years ago

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.

presentation|3 years ago

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.

cjonas|3 years ago

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

debaserab2|3 years ago

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.

somewhereoutth|3 years ago

> Over the years, the type system of TypeScript has grown from basic type annotations to a large and complex programming language.

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.

Etheryte|3 years ago

Hard disagree. For me, the proof is in the pudding. When writing regular Javascript, I need to run and debug my code often as I'm developing it to make sure all intermediate states make sense and that there's no edge cases I've missed. With Typescript I can often write code for hours without even starting it up, and when I finally do run it, it usually works correctly right out of the box.

jahewson|3 years ago

> fills your head and slows you down

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.

edgyquant|3 years ago

Have you worked with typescript? Adding strict types to JavaScript fixes pretty much all the complaints I use to have when working with frontend services/clients.

Typescript isn’t a “now you have two problems” anymore than types in any other language are.

dragonwriter|3 years ago

> Almost all bugs are logic bugs or inconsistent state bugs (thanks OOP!), almost none are type bugs.

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.

shepherdjerred|3 years ago

> Almost all bugs are logic bugs or inconsistent state bugs (thanks OOP!), almost none are type bugs.

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.

hither_shores|3 years ago

> Almost all bugs are logic bugs or inconsistent state bugs (thanks OOP!), almost none are type bugs.

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.

mckravchyk|3 years ago

Complexity or not, it's incredibly useful. To me the development experience is just superior, you feel more in control. No one likes building the app only to see "Cannot read properties of undefined" and then needlessly scratching head what's wrong. I have done a large refactor recently and it was such a breeze, with TS pointing out pretty much everything that needs to be fixed to complete it. Coding new features and sometimes it just works after the first build. TS is obviously the inevitable future since it offloads the stuff that the computer can do better and frees up the mental energy for the more creative stuff. The code is also more readable, when the types mean something there's less need for describing function parameters for instance, since you have a complete type definition of what that param represents and any comments made on those types are re-usable across functions.

gherkinnn|3 years ago

> Almost all bugs are logic bugs or inconsistent state bugs (thanks OOP!), almost none are type bugs.

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?

galaxyLogic|3 years ago

A simple trick with plain JavaScript is to give all arguments default values. That gives a pretty good insight for anybody reading the code as to what "type" of arguments the function expects.

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.

serverlessmania|3 years ago

Agree for the OOP part, most devs doing the transition from Java,C# to TypeScript are using the tool to port what they learned and apply it to a JS project and projects like Angular are encouraging this by enabling experimental stuff like decorators, but missing the real point behind TS, the type checking system which is to be fair, really awesome.

kixxauth|3 years ago

Sooooo agree. Let's layer an abstraction on top of an abstraction to abstract the abstraction.

nsxwolf|3 years ago

I currently have to occasionally contribute to a TypeScript codebase at work. I appreciate how much better it is than Javascript. When I write code as an outsider (Java and Go developer), I feel like I use the type system in sensible and readable ways. When I look at the code written by the native TypeScript experts in my company it is a bewildering, abstract, unreadable morass. I have no idea what's going on half the time.

solardev|3 years ago

Yeah, what a terrible syntax :(

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?

simlevesque|3 years ago

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.

krembanan|3 years ago

> It works great with the VSCode extension.

What do you mean? Which extension?

tunesmith|3 years ago

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?

zarathustreal|3 years ago

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

```

diroussel|3 years ago

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.

spion|3 years ago

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.

softfalcon|3 years ago

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.

pragmatic|3 years ago

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.

shepherdjerred|3 years ago

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.

gherkinnn|3 years ago

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

WHATDOESIT|3 years ago

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.

Cthulhu_|3 years ago

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.

sbf501|3 years ago

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.

schwartzworld|3 years ago

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.

int_19h|3 years ago

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.

Rapzid|3 years ago

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

lf-non|3 years ago

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.

uup|3 years ago

The Id<T> type defined in the first comment seems like a pretty good, albeit ideally unnecessary, workaround.

agentultra|3 years ago

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

AaronFriel|3 years ago

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

323|3 years ago

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.

323|3 years ago

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

iainmerrick|3 years ago

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

davidatbu|3 years ago

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/

melony|3 years ago

Does TypeScript support dynamic dispatch? JS by nature is dynamically typed you should be able to introspect at get the type at runtime.

likeclockwork|3 years ago

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.

YoannMoinet|3 years ago

I particularly love the interactivity of the training. So polished.

Can't get enough of the fireworks!

guggleet|3 years ago

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.

amadeuspagel|3 years ago

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?

kbr-|3 years ago

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.

KrishnaShripad|3 years ago

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

brundolf|3 years ago

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

d4mi3n|3 years ago

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.

danellis|3 years ago

I'm currently working on a project that uses both Typescript and Scala. The overlap in concepts is rather useful when switching contexts.

bwestergard|3 years ago

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.

downvoteme77221|3 years ago

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

cutler|3 years ago

Add another 1 to your army. We all know how Typelevel Scala ended.

Myrmornis|3 years ago

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.

SeriousM|3 years ago

Types are here to help but with this power comes great responsibility. The deeper you allow an input to be used in your library/method the more it makes sense to put a well defined type contract on it. Exhaustive type definitions may show that the author doesn't have a understanding of a required interface, abstractions or pattern to use. I use c# professionally for 20 years and I love the type system. It helps to tell the contract about the types and functions you expect yet you can overcomplicate things if you really about to. c# makes it "hard" to create method type definitions and you should use interfaces to achieve the contract definition. This helps to avoid the inline type definition as it's done in typescript. My approach is to use types in typescript as I'm used to in c#.

Benjamin_Dobell|3 years ago

One of the worst things about Next.js, Remix etc. is their file system driven routes. I really wish these frameworks would stop relying so much on hidden magic. Conventions are good, but as to why those conventions aren't in code is quite peculiar.

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) => {

presentation|3 years ago

remix-routes and next-type-safe-routes both allow for tacking on type safety here with a build step, not ideal but 90% of the benefit from that.

throw10920|3 years ago

> Expect<Equal<Hello, "World">>

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

robertwt7|3 years ago

Really interesting articles, will definitely have a look at night.

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

aussiesnack|3 years ago

Looks good. Given the course is unfinished, it's a shame the only way to get updates is via a third party corporate web site ("Twitter"). RSS would be the obvious way to go.

remram|3 years ago

Twitter seems like a good way to miss it. I don't check Twitter every day...

HellsMaddy|3 years ago

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.

kaladin_1|3 years ago

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

cercatrova|3 years ago

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.

seer|3 years ago

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.

NTARelix|3 years ago

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.

gherkinnn|3 years ago

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

SpikeMeister|3 years ago

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.

theteapot|3 years ago

Didn't know `Expect` existed. Can't find it docs?

gvergnaud|3 years ago

It's actually not in the standard library, but you can write it yourself:

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.

jheriko|3 years ago

reminds me of C++ templated types being used for similar... except in this case there is no performance advantage from removing run-time logic by force.

this kind of stuff is often confusing when working with teams. using simple dumb stuff is always the better option when you can.

willthefirst|3 years ago

Great content. Give it me to me as a daily-dose newsletter :)

arberx|3 years ago

There are only 3 chapters so far...

Ayc0|3 years ago

I love the content!!

theteapot|3 years ago

Course is probably great, but I find it really weird and unnecessary to describe Typescript type system as Turing complete. Who cares?

lolinder|3 years ago

People who are trying to statically describe the behavior of highly dynamic JavaScript code.

gitowiec|3 years ago

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!