spotifyovercastrssapple-podcasts

Comparing TypeScript and Elm's Type Systems

TypeScript and Elm have very different type systems with different goals. We dive into the different features and the philosophy behind their different designs.
October 25, 2021
#42

TypeScript's any vs. Elm's Debug.todo

TypeScript's any essentially "turns off" type checking in areas that any passes through.

In Elm:

  • You can get a type that could be anything with Debug.todo, but you can't build your app with --optimize if it has Debug.todo's in it
  • You will still get contradictions between inconsistent uses of a type that could be anything (see this Ellie example)

This Ellie example (with compiler error as expected) and this TypeScript playground example (with no error) show the difference.

Transcript

[00:00:00]
Hello, Jeroen.
[00:00:01]
Hello, Dillon.
[00:00:02]
So, what are we talking about today?
[00:00:06]
Today we're talking about TypeScript again, and we'll be comparing it with Elm.
[00:00:11]
But since comparing the two is a very big topic, we were considering just looking at
[00:00:16]
the type systems.
[00:00:18]
Yeah.
[00:00:19]
I think that's enough for an episode.
[00:00:20]
Knowing us.
[00:00:21]
Yeah.
[00:00:22]
So let's sort of clarify something.
[00:00:27]
What do we want listeners to take away from this?
[00:00:30]
Because I want to state that our goal is not to pick a winner, right?
[00:00:36]
It's a...
[00:00:37]
No, no, no.
[00:00:41]
For one thing, we're far too biased to be trusted with such a thing.
[00:00:45]
That is true.
[00:00:47]
We're just laying out some facts.
[00:00:48]
But also, I'm a big fan of this idea of not being an absolutist about which things are
[00:00:56]
better.
[00:00:57]
So it's far more useful to look at what are the goals, what are these tools best suited
[00:01:02]
for, and let's understand the design choices that make them well suited for these types
[00:01:07]
of things.
[00:01:08]
And I think that is very much the way that I think about the TypeScript versus the Elm
[00:01:13]
type systems is TypeScript...
[00:01:17]
I've said this before on the podcast.
[00:01:19]
To me, TypeScript is extremely elegantly designed to solve the exact problem it solves, which
[00:01:25]
is Elm is extremely elegantly designed to solve the problem it solves.
[00:01:28]
And they solve two very different problems.
[00:01:31]
So Jeroen, I promised you a quote, which we both agreed was better to save for on air
[00:01:38]
rather than reading it on the pre call.
[00:01:41]
I'm so excited to finally listen to it.
[00:01:44]
Yes, now that we've gotten your expectations high, here you go.
[00:01:48]
So this is from a GitHub issue in the TypeScript repo.
[00:01:51]
And someone is asking the question, what I'm interested in knowing is whether the TypeScript
[00:01:57]
community slash team consider type checking unsoundness as something that should be fixed.
[00:02:04]
And a TypeScript core team member is saying, just speaking specifically to this point,
[00:02:09]
100% soundness is not a design goal.
[00:02:13]
Soundness, usability and complexity form a trade off triangle.
[00:02:18]
There's no such thing as a sound, simple, useful type system.
[00:02:21]
Now, I'm not sure I agree with that last point, unless it's specifically within the context
[00:02:28]
of what TypeScript is doing, which is adding types within the semantics of JavaScript,
[00:02:34]
and within the ecosystem of existing JavaScript code.
[00:02:37]
In that case, yes, I think that's true.
[00:02:40]
But in the broader sense, I think that Elm gives a pretty good case that you can have
[00:02:45]
a sound, simple and useful type system, right?
[00:02:48]
Can you elaborate what you mean with soundness?
[00:02:50]
Or what they mean with soundness?
[00:02:52]
Sure.
[00:02:53]
Yeah, well, I think soundness means, so like they give an example in this issue, which
[00:02:58]
I'll link to, you know, where they're adding something to an array.
[00:03:04]
And you know, you can kind of fool the type system.
[00:03:08]
So there are a number of ways that you can fool the type system in TypeScript.
[00:03:14]
But basically, like, I think Elm and TypeScript are working from two opposite ends.
[00:03:21]
Elm starts from everything must be proven, and is considered to be incorrect types until
[00:03:29]
I can guarantee it otherwise.
[00:03:31]
Like, unless I can convince myself that the types are correct, I'm going to reject it.
[00:03:39]
And TypeScript goes from the opposite side.
[00:03:40]
It says, I'm going to assume that this is a working application, unless I can find some
[00:03:47]
evidence that contradicts that.
[00:03:49]
So it's starting from, it's almost like, you know, innocent until proven guilty, or guilty
[00:03:57]
until proven innocent.
[00:03:58]
I think that Elm, in Elm, types are guilty until proven innocent.
[00:04:04]
And in TypeScript, they're innocent until proven guilty.
[00:04:07]
And that is because they're working on very different styles of code bases.
[00:04:12]
Like, Elm starts from scratch.
[00:04:14]
So if you only use the types that Elm has in its core libraries, and the compiler knows
[00:04:21]
about them and knows that it can give you those guarantees, then it's a good idea to
[00:04:27]
start with something that can be proven to be right.
[00:04:31]
Right.
[00:04:32]
That is sound.
[00:04:33]
Whereas with TypeScript, you start basically with the JavaScript code base.
[00:04:36]
Or at least that is how it is designed for.
[00:04:40]
I don't know if it still makes a lot of sense for projects that start with TypeScript.
[00:04:47]
But at least that's the original goal.
[00:04:49]
So you start with TypeScript, and you have no guarantees at all.
[00:04:53]
And then you try to prove things correct where you can, and show the errors where you cannot.
[00:05:01]
Where you can show the errors.
[00:05:02]
Yeah.
[00:05:03]
Right.
[00:05:04]
So if you're starting the idea of FFI, like foreign function interface, being able to
[00:05:09]
call directly into JavaScript, that is the big differentiator, right?
[00:05:14]
Because if you're starting with a fresh brand new code base, in Elm, you can't pollute the
[00:05:20]
sound typed environment where every type you have you know is correct.
[00:05:26]
You can't pollute that with unsoundness because you can't directly invoke unsound type systems.
[00:05:34]
But with TypeScript, the design decision, which I think makes perfect sense for TypeScript,
[00:05:39]
is sure, go ahead, call JavaScript all you want.
[00:05:42]
And oh, I've got some NPM library that doesn't have TypeScript typings?
[00:05:47]
That's okay.
[00:05:49]
We'll just help you out as much as we can around that, and we'll just consider the types
[00:05:55]
to be untyped there.
[00:05:57]
And we probably won't be able to help you much for that untyped code.
[00:06:01]
But we can for the rest of the application.
[00:06:03]
We can for the rest of the application.
[00:06:04]
So it's seen as sort of like a bonus, the types, rather than everything must be proven
[00:06:13]
and the entire ecosystem is designed around this, right?
[00:06:16]
Because you can just publish a new...
[00:06:19]
You can publish an NPM package that uses eval.
[00:06:23]
What type does it do?
[00:06:25]
TypeScript can't help you there.
[00:06:26]
It could eval a string from user input, and it could parse some JSON.
[00:06:33]
So these are the core design decisions that guide Elm versus TypeScript.
[00:06:39]
And I think this is what I hope people will come away from listening to this episode with,
[00:06:46]
is this understanding of the different design goals.
[00:06:49]
And take that for whatever purposes serve you.
[00:06:53]
If you're not an Elm developer, that helps you kind of think about where to be extra
[00:06:59]
careful when you're writing TypeScript code and how to think about these trade offs.
[00:07:04]
That's great.
[00:07:05]
If you're not a TypeScript developer, if you only do Elm, and you need to use a little
[00:07:09]
bit of JavaScript, hopefully this helps you understand the design decisions and goals
[00:07:16]
behind Elm a little bit better.
[00:07:17]
So the TypeScript type system is, in my opinion, and I'm guessing yours as well, a lot more
[00:07:23]
complex than Elm's.
[00:07:25]
And partially the reason for that is because it needs to type check JavaScript.
[00:07:30]
And JavaScript can do a lot of things.
[00:07:33]
So if you want to have a type system that doesn't limit on what JavaScript can do, or
[00:07:38]
at least when it works, then you do need to have a very powerful type system.
[00:07:45]
So that's something to keep in mind.
[00:07:48]
Oh, it's very complex.
[00:07:50]
It can do a lot of things and that is potentially very hard to annotate in your types.
[00:07:56]
But it's there for a reason.
[00:07:58]
It's because JavaScript has a lot of features.
[00:08:01]
Yes, it does.
[00:08:03]
And also to further back up this idea that we're not trying to prove which type system
[00:08:12]
is better, but we're just looking at their design goals.
[00:08:16]
I think that TypeScript is a very cool type system and it has some features that I think
[00:08:22]
are really neat, which the Elm type system does not have.
[00:08:25]
Does that mean the Elm type system should have them?
[00:08:27]
Not necessarily.
[00:08:28]
They're just features that are very well suited to the specific problems that TypeScript is
[00:08:33]
solving and they're really cool and they're good to be aware of.
[00:08:35]
So for example, in Elm, if you change the value of a string, like if you change concrete
[00:08:43]
values within the same type, it will never change whether the types compile or give a
[00:08:50]
compiler error.
[00:08:51]
That will never happen.
[00:08:53]
Whereas in TypeScript, that can happen.
[00:08:56]
So TypeScript is able to actually look at concrete values, which the Elm type system
[00:09:04]
does not have the ability to do.
[00:09:06]
That gives you some really interesting capabilities.
[00:09:10]
If you're a TypeScript library author or application author.
[00:09:15]
So by the way, was the quote worth it your own?
[00:09:18]
It was worth it.
[00:09:20]
I think sometimes it's easy to sort of, TypeScript can be an easy target to be like, oh, TypeScript
[00:09:30]
isn't a sound type system.
[00:09:32]
But I think it's good to have primary sources to say, no, this is the stated design goal
[00:09:39]
of TypeScript.
[00:09:40]
This is not designed to be unsound.
[00:09:42]
And that's not a bad thing.
[00:09:44]
It's just a different goal and understand that.
[00:09:46]
And this is not my words.
[00:09:48]
This is the words of the TypeScript team.
[00:09:50]
I mean, it could try to, but then it would be a very different language and you would
[00:09:55]
have a lot more errors, I'm guessing.
[00:09:57]
Yeah, a lot more.
[00:09:59]
So yeah.
[00:10:00]
And one of the things they talk about is, you know, just the idea of mutation and how
[00:10:07]
like, how do you square mutation with sound types?
[00:10:11]
You know, is it possible?
[00:10:13]
Yes, but it's an added complication.
[00:10:16]
And it's not one that, that's, so we'll get into this later in the episode, but that's
[00:10:22]
one of the avenues of introducing unsoundness into TypeScript types.
[00:10:27]
Yeah, I guess one thing you could do is to, instead of being a superset of JavaScript,
[00:10:33]
is take a subset of JavaScript and add type annotations.
[00:10:39]
So remove mutations from JavaScript and then add types.
[00:10:43]
And then the language would be a lot simpler, but you can do a lot less things with it.
[00:10:47]
And it's not compatible with JavaScript libraries that are out there on NPM and stuff like that.
[00:10:53]
Exactly.
[00:10:54]
Yeah, yeah, absolutely.
[00:10:55]
All right.
[00:10:56]
Well, should we get to the basics a little bit?
[00:10:59]
That sounds good.
[00:11:00]
Where should we start?
[00:11:01]
How basic do we want to get here?
[00:11:03]
Well, I guess let's start with the primitives.
[00:11:05]
Yeah.
[00:11:06]
So TypeScript has three primitives.
[00:11:09]
It has strings, numbers, and Booleans, which Elm also has except for number because it
[00:11:16]
has int and floats.
[00:11:18]
Although secretly, it's actually just JavaScript numbers.
[00:11:26]
Everything is JavaScript under the hood.
[00:11:28]
So at least until it stops compiling to JavaScript only.
[00:11:33]
Right.
[00:11:35]
Yeah.
[00:11:37]
It's got those primitives.
[00:11:40]
If you haven't seen a TypeScript annotation before, you can take let name equals Yeroon
[00:11:45]
and you can turn that into something with a type annotation with let name colon string
[00:11:53]
equals Yeroon.
[00:11:54]
But the TypeScript type system can also do type inference.
[00:11:57]
So you don't strictly need to put type annotations everywhere similar to Elm.
[00:12:02]
But it's probably a good idea.
[00:12:04]
It's probably done idiomatically.
[00:12:07]
It's done pretty much everywhere in idiomatic Elm code.
[00:12:10]
In my opinion, some people might have different opinions on that.
[00:12:14]
But in TypeScript it's not.
[00:12:16]
Yeah.
[00:12:17]
I wrote rules to add type annotations everywhere possible.
[00:12:20]
So yeah.
[00:12:21]
Yeah.
[00:12:22]
I use that rule often.
[00:12:24]
And actually, I used to not put type annotations on my let bindings in Elm.
[00:12:30]
But that rule sort of changed my thinking on that.
[00:12:34]
And now I just put them everywhere.
[00:12:35]
Because the more hints you can give to the compiler where you're like, no, no, this is
[00:12:41]
definitely the type.
[00:12:42]
I know this is the type I want here.
[00:12:44]
The more specific the compiler can be to pointing you to the problem if something goes wrong.
[00:12:49]
So that's how I think of it.
[00:12:50]
Yes.
[00:12:51]
Exactly.
[00:12:52]
So, if you put a big blob of text about an error in Elm, if you don't put type annotations
[00:12:58]
where it's needed, because it will say something like, oh, this big record has this field that
[00:13:05]
is not the right type.
[00:13:06]
But if you put the annotation on a value that you put before and that it notices that it's
[00:13:13]
wrong over there, then it will point the error to a smaller section.
[00:13:18]
So the error messages are much better if you put type annotations.
[00:13:23]
And this I don't know about TypeScripts.
[00:13:25]
And I was wondering whether you know, are the error messages better when you put them
[00:13:31]
everywhere?
[00:13:32]
Do you have the same kind of problem you do in Elm?
[00:13:36]
I think so.
[00:13:37]
I mean, I think that's just an inherent property of, you know, that you're saying, like, you're
[00:13:45]
giving more information for the type system to say, this is supposed to be this type.
[00:13:51]
And it needs to square everything with all that information.
[00:13:54]
So it's going to point the problem to somewhere else, as long as all those type annotations
[00:13:59]
are correct.
[00:14:00]
So, yeah, I think that's the same as with Elm.
[00:14:02]
But idiomatically, I think in the TypeScript ecosystem, I think it's a lot more common
[00:14:08]
to just say, like, oh, wow, this type system is great.
[00:14:11]
I don't have to put annotations everywhere, which as an Elm developer, I tend to think
[00:14:16]
of it more as like, wow, this type system is great.
[00:14:21]
It's smart enough to figure things out.
[00:14:25]
And the tooling can help me put type annotations places to lock them in as I do my work so
[00:14:31]
that I can give it all the clues possible to help it give me better error messages.
[00:14:37]
But what I want is I want it to catch mistakes and point me to the problem so that when something
[00:14:44]
has a compiler error, it's the shortest path possible to fix things.
[00:14:49]
And I think that's just a different mindset between Elm and TypeScript.
[00:14:52]
I don't know if it's only for TypeScript.
[00:14:55]
So the last time I used Java was a very long time ago, like more than 10 years ago.
[00:15:00]
And when I was using it, you had to specify the type of everything, of every variable
[00:15:06]
that you set.
[00:15:07]
And then later, they added the feature where you could say, val x equals blah, blah, blah,
[00:15:15]
where the compiler would infer the type.
[00:15:17]
So I'm guessing the compiler is still pretty strict.
[00:15:21]
So if it could not figure something out, it would tell you.
[00:15:24]
But since everything has annotations everywhere, I'm guessing it's pretty good at that.
[00:15:30]
But still, yeah, when I put type annotations on let variables, it's partially for documentation.
[00:15:39]
So that I don't have to, if I say something like x equals function call, then I don't
[00:15:46]
want to have to look to the function definition to know what the type of my result is, especially
[00:15:52]
if it contains something like generics, where then I would also have to check for the arguments.
[00:15:57]
And that can be a rabbit hole.
[00:16:00]
Right.
[00:16:01]
But if I put a type annotation in there, and the type of the function call changes, I kind
[00:16:06]
of want to know.
[00:16:07]
Because that will change how I respond where I assign the variable.
[00:16:15]
That's a great point.
[00:16:16]
Yes.
[00:16:17]
Right.
[00:16:18]
Yeah.
[00:16:19]
You don't want to be like trying to fix things and then without realizing it, change the
[00:16:23]
type of a variable in your let bindings.
[00:16:25]
Yeah.
[00:16:26]
I mean, it will probably give an error somewhere else.
[00:16:28]
Right.
[00:16:29]
But if you're in the middle of a challenging type puzzle that you're solving, and you didn't
[00:16:34]
realize then, yeah, you want to have put those as fixed points.
[00:16:39]
So you can change one small thing at a time when you're trying to solve a problem.
[00:16:44]
Yeah.
[00:16:45]
Then I'm guessing in TypeScript, it can be dangerous because if the function call somehow
[00:16:51]
starts returning any compared to something else before, now you have polluted your function
[00:16:58]
call location.
[00:17:00]
Yes.
[00:17:01]
So I know we were supposed to talk about basics, but let's talk about any.
[00:17:06]
Yes.
[00:17:07]
Okay.
[00:17:08]
So there are several points that just came up there.
[00:17:11]
I want to touch on one thing quickly, which is you mentioned like Java and the explicitness
[00:17:19]
and verbosity of Java types.
[00:17:21]
I think to some extent like that, that's what a lot of people push back on when they're
[00:17:27]
against typed languages.
[00:17:29]
I think a lot of that came from a lot of these design decisions of Java.
[00:17:33]
So there were many places where you would write duplicate information and you would
[00:17:40]
define a type and then you would invoke a constructor and you'd have to put the type
[00:17:45]
again and it seemed like repetitive information and this sort of thing.
[00:17:50]
Like one of the things that a lot of people had trouble with Java is that almost everything
[00:17:56]
is like a nominal type in Java rather than a structural type.
[00:18:00]
So a nominal type being nominal as in name, nom, nom, if you prefer.
[00:18:08]
Thank you.
[00:18:09]
That is basically saying that if it doesn't refer to the specific identifier of like a
[00:18:16]
constructor, then it's not the same thing, which in Elm that would be like a custom type.
[00:18:21]
If you'd find a custom type in a particular module, if that custom type has a value of
[00:18:26]
a particular record type inside of the constructor of the custom type variant, then it doesn't
[00:18:32]
matter if you create something that has the same record with the same fields.
[00:18:38]
It didn't use that identifier as the constructor to construct that variant and therefore it's
[00:18:43]
not that type.
[00:18:44]
And so you can do that in Elm.
[00:18:46]
In Java, it's almost like everything is that.
[00:18:51]
You can create these like map like structures and array lists and things like that.
[00:18:56]
But even those are like a data structure that is nominally typed and can have key value
[00:19:02]
pairs if you want or whatever.
[00:19:03]
But you can't sort of just put a key value data structure and then, oh, okay, now I see
[00:19:11]
it has these fields with these types.
[00:19:13]
And so TypeScript has that and Elm has that.
[00:19:17]
It turns out that's very nice because sometimes you're like, I don't care how it was constructed.
[00:19:22]
I just need these values or maybe even a subset of these values like extensible records in
[00:19:27]
Elm.
[00:19:28]
I need a record and I don't care what other data it has, but I need a first name and a
[00:19:31]
last name to be strings.
[00:19:33]
And that's all I care about.
[00:19:34]
So the annoying part in Java is that you can't have anonymous records basically.
[00:19:39]
Yeah, exactly.
[00:19:40]
You would have to have a first last name interface or class or thing like that.
[00:19:47]
And something would, you'd have to say implements first last name or, you know, and that people
[00:19:53]
get really tired of that with Java.
[00:19:55]
And I feel like that's what a lot of the pushback against typed languages came from.
[00:19:59]
And then people get into a language like Elm and they're like, wow, actually not only is
[00:20:04]
the type system more lightweight and easier to do things like structural typing and that
[00:20:10]
sort of thing.
[00:20:11]
It feels like I'm fighting against things less to get the right types, but also it helps
[00:20:17]
me more when my types are actually incorrect.
[00:20:20]
Not just like I didn't put the correct identifier name, but actually like I have a problem with
[00:20:26]
my types.
[00:20:27]
It helps me more and I can actually trust it when it says the types are right because
[00:20:31]
I don't have array index out of bounds issues and I don't have unexpected exceptions and
[00:20:36]
things like that.
[00:20:37]
So anyway, I think that's like some important history that like a lot of these decisions
[00:20:43]
are learning from some of the lessons of like earlier versions of Java, which as you said,
[00:20:49]
like Java itself is learning from some of these things too.
[00:20:52]
Okay.
[00:20:53]
So that's my rant about that.
[00:20:55]
But so you wanted to talk about any types.
[00:20:58]
Sure.
[00:20:59]
So in JavaScript, nothing is typed.
[00:21:03]
Right.
[00:21:04]
So the thing that comes closest to that is the any type where the types basically has
[00:21:10]
no information about what the type can be.
[00:21:15]
And it uses that for things where it does not have any type definitions.
[00:21:19]
So something like packages from NPM, sometimes it doesn't have any information.
[00:21:24]
So it says everything's any arguments or any return types or any, everything's any.
[00:21:30]
And that is something that is contagious.
[00:21:33]
Right?
[00:21:34]
Exactly.
[00:21:35]
Yes.
[00:21:36]
So if you have something that is that is any somewhere and then you do stuff on it, well,
[00:21:42]
that also becomes any.
[00:21:43]
I'm not entirely sure about how contagious it is and how that contagion works, but that's
[00:21:49]
how it feels at least.
[00:21:51]
Well, I mean, it's, I think it's pretty much exactly the same as if you used debug.to do
[00:21:57]
in Elm.
[00:21:59]
You know, the obvious difference being one debug.to do will never actually run.
[00:22:05]
It'll just halt the program.
[00:22:07]
And two, you can't compile it with the optimize flag in Elm.
[00:22:12]
Right.
[00:22:13]
So yeah.
[00:22:14]
In production mode.
[00:22:15]
Right.
[00:22:16]
But otherwise, I think it's the same.
[00:22:17]
There may be some slight differences, but effectively like the any type.
[00:22:21]
I mean, in Elm, it's the same thing where it just says this could literally be anything.
[00:22:25]
I don't know.
[00:22:26]
Like, oh, you mean anything like it could be a function that you could call.
[00:22:29]
It could be a function that takes this thing and turns it into this.
[00:22:33]
It could be a string.
[00:22:34]
Yeah, it could be anything.
[00:22:35]
So you could like call it as a function.
[00:22:37]
You could pipe it to something.
[00:22:39]
You could, it could be an opaque type.
[00:22:42]
It could be, it could be never, it could be anything, even if that's not even possible,
[00:22:48]
but the Elm type system will just say, okay, well, I guess this could be any value.
[00:22:52]
So it fits anywhere.
[00:22:53]
Yeah.
[00:22:54]
And then it will only complain if it tries to infer something based on it and finds out,
[00:23:00]
oh, okay, well this now needs to be a string because you use it where it's, where you need
[00:23:06]
a string.
[00:23:07]
Exactly.
[00:23:08]
And then it finds that some other place where the value also needs to be an integer or something.
[00:23:12]
Exactly.
[00:23:13]
And those are two contradictory values.
[00:23:15]
So you use an any type in two places that are contradictory and the Elm type system
[00:23:21]
will say, wait a minute, like, okay, I know, I know this could be anything, but it can't
[00:23:25]
be any two things at once.
[00:23:27]
And therefore something's wrong here.
[00:23:29]
So that is for Elm where, yeah, we do have any, but only for development mode, just to
[00:23:36]
help us work on something before it gets sent to production.
[00:23:40]
But what about TypeScript?
[00:23:41]
So how does it handle those conflicts of inference?
[00:23:47]
Let me do a quick example here in the playground and see what happens.
[00:23:51]
I'm curious too now.
[00:23:52]
From what I can see in my playground, it doesn't seem to be doing that kind of inference.
[00:23:59]
I'm seeing the same thing.
[00:24:00]
So I did an example where I said, let a be any type.
[00:24:06]
And then I defined a function that takes a Boolean and a number.
[00:24:12]
And I called that function with a and a.
[00:24:16]
And it says, yeah, that's fine.
[00:24:19]
So I think any does behave differently.
[00:24:22]
Well, I'll try to find some like posts on exactly how that type resolution for any works
[00:24:28]
and try to link to it.
[00:24:30]
But yeah, it appears that any is more permissive than an unbound type variable in Elm that
[00:24:39]
you can get with debug.to do that just says this could be anything.
[00:24:42]
So they're not exactly equivalent, it looks like.
[00:24:44]
That's very interesting.
[00:24:46]
Or rather the compiler does not do the same kind of checks.
[00:24:52]
Maybe at the moment, because every time I hear about a new TypeScript version, it seems
[00:24:56]
to do more and more checks, more powerful checks.
[00:25:00]
So maybe at some point.
[00:25:01]
Yeah, very interesting.
[00:25:03]
Okay, so a couple things to mention on the topic of any.
[00:25:07]
One is there's a type called unknown in TypeScript.
[00:25:10]
And unknown is very different from any.
[00:25:13]
In some ways, it's similar.
[00:25:14]
In some ways, it's different.
[00:25:16]
Unknown is saying you're going to have to prove what this type is before you can use
[00:25:21]
it.
[00:25:22]
So for example, so I think one of the most important things to know as a TypeScript developer,
[00:25:30]
like someone who's writing TypeScript code, is that any is not just, you know, wherever
[00:25:37]
you explicitly use any.
[00:25:40]
Any happens all over the place, most prominently and most commonly in JSON.parse.
[00:25:46]
When you do JSON.parse, it returns a type any.
[00:25:49]
In fact, that's not even that's not even accurate, because JSON.parse will never return a function.
[00:25:58]
But a function exists in that set of types any.
[00:26:03]
So that's not good.
[00:26:04]
So it's not even being specific about it could be any JSON type string, bool, number, array
[00:26:12]
of those things, object of those things.
[00:26:18]
It's just yeah, it could be anything it could be a could be a function.
[00:26:21]
So when you do JSON.parse, be aware that that introduces a type and unsound type, as you
[00:26:29]
said, that is contagious throughout your application.
[00:26:32]
So so you know, there are tools like IO dash TS, and other similar tools that help do a
[00:26:40]
pattern similar to the JSON decoding pattern in Elm.
[00:26:46]
But so unknown is a really useful type to be aware of.
[00:26:49]
But But it's very important to realize that many of the published like typings for for
[00:26:55]
core JavaScript functions like JSON.parse return in any type.
[00:27:00]
It's very common for NPM typings that are published.
[00:27:05]
So a lot of NPM libraries will either come with TypeScript types when you install them
[00:27:10]
with NPM install, or they will have a separate set of typings that you can install separately
[00:27:15]
with this definitely typed package, I'll link to this thing where you can search for packages
[00:27:20]
that have this definitely typed thing.
[00:27:22]
But it's very common for those to use any as well.
[00:27:26]
And as far as I know, there's not really a way to warn you if you're pulling in an any.
[00:27:33]
Yeah, I mean, there's no implicit any, but it doesn't help with that, right?
[00:27:38]
Exactly.
[00:27:39]
Yeah.
[00:27:40]
So no implicit any is like an option you can set in your TS config.json.
[00:27:44]
And you should definitely use the strictest possible settings for that, including strict
[00:27:49]
mode, but but you should be more strict than that I write about that a little bit in a
[00:27:54]
blog post that I'll share called TypeScript spline spots.
[00:27:57]
But yeah, so no, no implicit any will prevent you from saying like function greet takes
[00:28:04]
a name and then doing something with it.
[00:28:07]
And it'll say, Hey, since you didn't put a type annotation on the parameter name, I'm
[00:28:14]
implicitly assuming that that's an any type.
[00:28:16]
So the TypeScript is saying if you would like to use an any type there, which I just inferred
[00:28:22]
that to be because you didn't explicitly give a type to the parameter because sure, you
[00:28:27]
treat it like a string, but you can treat a lot of things like a string you can in in
[00:28:31]
JavaScript, you can concatenate an object and a string and that's fine.
[00:28:36]
As far as JavaScript semantics go, you can use a you can use a number or an object in
[00:28:44]
a conditional check if object if number and those semantics are proper JavaScript semantics,
[00:28:51]
they're they're confusing and they're often foot guns.
[00:28:55]
And that's one of the like, so this is an important thing to note is that the soundness
[00:29:00]
of Elm, it's more than just soundness, it's also like explicit semantics where Elm will
[00:29:06]
say, Hey, if you want, if you want a Boolean check, I know you might be passing like some
[00:29:13]
maybe value and saying if maybe value, but that's not how you do that in Elm.
[00:29:18]
If you'd like to say if it if it is not nothing, then you need to do that explicitly, you probably
[00:29:24]
actually want to do a case statement.
[00:29:25]
But if you really want to do that, explicitly turn it into a bool for me, because that's
[00:29:30]
probably a foot gun that so Elm will prevent you from unintentionally passing different
[00:29:36]
values places.
[00:29:37]
And that's something that makes it safer.
[00:29:39]
Strictly speaking, in terms of soundness, it doesn't really affect the soundness of
[00:29:43]
the type system.
[00:29:44]
But practically, it does.
[00:29:46]
Wait, let's get back to the grid function that you mentioned.
[00:29:50]
If you do not put any type annotations on the arguments, they are all considered any
[00:29:56]
exactly, exactly.
[00:29:58]
Because, well, maybe not regardless of the implementation, but but if you're concatenating
[00:30:04]
two things, then it can't infer the type based on that.
[00:30:09]
So sure, you're concatenating two things, but I can concatenate a number with with a
[00:30:16]
string in JavaScript, JavaScript lets you do that.
[00:30:19]
So sure, you're saying hello plus name, but I can't infer that that must be a string based
[00:30:27]
on that.
[00:30:28]
Whereas Elm can, Elm can infer that and it's not going to say, Oh, I don't know what type
[00:30:32]
this is, it's going to say, Oh, that must be a string because you're doing string plus
[00:30:36]
plus something that thing must be a string.
[00:30:39]
TypeScript can't do that.
[00:30:41]
So if you say no implicit any in your type script configuration, then it will try to
[00:30:47]
say it's unknown or it will let you know, hey, please add a type annotation.
[00:30:51]
Is that it?
[00:30:52]
Yeah, it will give you an error or a warning if you I think an error if you have no implicit
[00:30:56]
any and it will say, hey, I'm inferring this, this type, the name parameter to be of type
[00:31:03]
any, if you would indeed like it to be type any, then you must explicitly write that and
[00:31:08]
you can write that you can say, name, colon, any, and it'll say, Okay, fine, that's an
[00:31:13]
explicit any, I was giving you errors for implicit any.
[00:31:17]
And now you've made that explicit, and that's fine.
[00:31:20]
And so, of course, you could also explicitly put a different type like string in is the
[00:31:26]
type annotation for the parameter.
[00:31:27]
So that's that's no implicit any, but that doesn't prevent you from doing JSON dot parse
[00:31:34]
on something.
[00:31:35]
And suddenly, what value to get?
[00:31:38]
Do you get back any and now you, you know, as far as I'm aware, there's, there's no TypeScript
[00:31:44]
rule, not even like an ES lint type rule for preventing that if I'm mistaken, if there
[00:31:52]
is something then please, please let me know, because I'd love to learn about that.
[00:31:56]
But as far as I know, you can't, you can't prevent that or have some something that makes
[00:32:02]
you like, market explicitly, I think that would be really nice to, you know, to get
[00:32:07]
a warning if you do JSON dot parse, to at least have to explicitly write any if you
[00:32:14]
did, to acknowledge, I know I'm getting an any back here.
[00:32:18]
I'm guessing it would be useful, but I'm also guessing that it would be kind of annoying.
[00:32:24]
But I'm sure it depends on how likely are you to fight with your compiler to get things
[00:32:29]
right.
[00:32:30]
So, so if you are doing something pretty complex, and you don't know how to type it, because
[00:32:35]
JavaScript is very complex and you can do very complex things with it, then potentially
[00:32:41]
like the only thing that you can do is to say it's any.
[00:32:45]
Exactly.
[00:32:46]
Or to go through the whole documentation to find exactly how to type it.
[00:32:50]
And that can be very complex.
[00:32:52]
I think you're spot on.
[00:32:53]
And I think another, another thing is like, what if you do JSON dot parse and pass that
[00:33:00]
down to something else and that passes something else and returns type and then through that
[00:33:04]
chain of function invocations, you end up with something that's not any.
[00:33:08]
Well, is it going to tell you anytime you use something that returns an any and force
[00:33:13]
you to assign it to a variable somewhere or how would that even work?
[00:33:18]
Yeah.
[00:33:19]
I mean, that wouldn't be practical.
[00:33:20]
Right.
[00:33:21]
Right.
[00:33:22]
So, so I think that the, you know, TypeScript language is just like, Hey, that's, that's
[00:33:27]
just part of it.
[00:33:28]
Like you get any somewhere.
[00:33:30]
And really I think, I think the philosophy is it's really more like a, a, a linter where
[00:33:35]
it it's a bonus that it catches some things and it does its best effort.
[00:33:40]
And again, I don't, I don't mean this as a value judgment.
[00:33:45]
I just, I just, I think that when you're writing TypeScript, which I, I use TypeScript and
[00:33:50]
I think it's, I think it's great, but I think you need to be aware of these things when
[00:33:54]
you use them, not just think like, Oh, it's typed.
[00:33:57]
No, it's, it's typed in a very different way.
[00:33:59]
And it's pragmatic for the goals it's solving for.
[00:34:02]
But so that's, that's why I wrote that post TypeScript Spine Plots.
[00:34:07]
And it's, it's one of my, one of my favorite posts that I've written.
[00:34:11]
It's a short little one, but my, my goal with that post was for myself as much as for anybody
[00:34:17]
to document these these cases to be aware of that could end up shooting you in the foot.
[00:34:25]
I guess what you're saying is it really depends on how you view it.
[00:34:29]
Like sure you can, if you're just trying to write JavaScript and then add type annotations
[00:34:35]
to catch some errors, then yeah, it's a lender in a way.
[00:34:39]
A very powerful one at that, but it has its limitations.
[00:34:43]
But if you're thinking that you're not developing JavaScript, but you're writing TypeScript,
[00:34:50]
then I think you're going to write your code differently.
[00:34:53]
You're going to write it probably in simpler ways in ways that you can, that you know how
[00:34:57]
to type them and you will do less dynamic things.
[00:35:02]
You will do less dynamic constructs and you will be more explicit in everything that you
[00:35:06]
do, which I think is the way to go.
[00:35:08]
But that's from a biased opinion as well.
[00:35:11]
Yeah, no, I mean, that's why I wrote this blog post for myself when I'm writing TypeScript
[00:35:18]
code because I wanted to, to just like have a quick reference of what are the things that
[00:35:24]
introduce unsoundness, not because I'm never going to use them, but because I, in my mind,
[00:35:30]
I want, I want a bright red ring in my brain around it whenever I see JSON.parse.
[00:35:37]
And you know, so for me writing this blog post succeeded in doing that in my brain where
[00:35:42]
I, you know, anytime I see JSON.parse, I'm like, uh oh, there's a, there's an any that's
[00:35:47]
trickling through my types now.
[00:35:49]
Same as when you call HTTP requests, right?
[00:35:52]
Yeah, yeah, cause you, I mean, cause it's JSON.parse under the hood.
[00:35:55]
JSON.parse is just used so many places and same when you install in a typings of an NPM
[00:36:02]
package and you know, so yeah, you have to, you have to be very skeptical of typings that
[00:36:11]
come from the outside.
[00:36:12]
You have to be a little bit paranoid.
[00:36:15]
So we mentioned that unknown is a better alternative to any when possible at least, which I think
[00:36:21]
it's most cases, but when you use unknown, what you need to do is to narrow the type.
[00:36:27]
You need to tell TypeScript how do you, how can I tell you that this will be a string
[00:36:33]
or that this will be something else?
[00:36:36]
And what you do for that is to say things like if a value contains, has a property to
[00:36:43]
uppercase or, or you call things like array.isArray or use type of stuff like that.
[00:36:50]
Right.
[00:36:51]
Usually you'd want to do type of rather than checking for a property on string.
[00:36:55]
Yeah.
[00:36:56]
But yeah, yeah, yeah.
[00:36:57]
You can just say type of, and it, and this is one of the really interesting things about
[00:37:00]
TypeScript is it, it uses control flow analysis in order to narrow types as you, as you're
[00:37:06]
saying.
[00:37:07]
Yeah.
[00:37:08]
And I noticed that in Elm, you don't have any of those.
[00:37:11]
The only thing that you have is better matching.
[00:37:14]
Yes, exactly.
[00:37:15]
You can have validation functions, but then how do you use those values?
[00:37:19]
You use better matching or things like maybe that with default or result that with default,
[00:37:25]
but then you lose some information.
[00:37:27]
Right.
[00:37:28]
And under the hood it's pattern matching anyway.
[00:37:30]
Right.
[00:37:31]
Because they're coming edit.
[00:37:32]
And again, you know, I mean, I guess I, I guess I sound like a broken record here, but
[00:37:37]
I always, I always like to think, you know, what are, what are the design constraints
[00:37:43]
and the problems that the author is trying to solve for, right?
[00:37:46]
And in the case of the TypeScript compiler, like what, what problem are they trying to
[00:37:50]
solve?
[00:37:51]
And if you get in the shoes of the compiler authors, the problem they're trying to solve
[00:37:55]
is well, we have an untyped language, JavaScript, and what we have, what does code do now?
[00:38:03]
Cause we want to fit into that.
[00:38:05]
Well, what people say, if value is no, else if type of string else if type of whatever,
[00:38:14]
and you know, you can do that to, to check the types.
[00:38:17]
Well, okay, well we can do some, some control flow analysis to, to narrow the types in those
[00:38:23]
cases based on runtime checks.
[00:38:26]
Whereas Elm is a language built from the ground up to be a type safe and type checked.
[00:38:31]
And so Elm says, actually I have these constructs specifically for narrowing types.
[00:38:38]
And actually in, in Elm, a very important distinction between Elm and TypeScript is
[00:38:42]
that Elm does not have untagged union types.
[00:38:46]
It only has tagged union types, which means, whereas in TypeScript you could say if type
[00:38:52]
of value equals, equals, equals string, then do something else.
[00:38:57]
If type of value equals, equals, equals object in Elm, there's no such thing because if it's
[00:39:02]
a string, it's a string.
[00:39:03]
If it's a maybe string, then okay.
[00:39:06]
Case of these two variants, but it's actually not a maybe string.
[00:39:10]
It's a, it's a maybe with one of two variants, but it's not like a string or an object or
[00:39:16]
a it's only one type of thing.
[00:39:19]
You can't say it's a string or a number.
[00:39:23]
There's no such thing.
[00:39:24]
Yeah.
[00:39:25]
Which I think is actually a very cool feature of TypeScript that you can have a true union
[00:39:31]
in the sort of like mathematical set theory sense of the term where you could have a function
[00:39:37]
argument that accepts, you know, it accepts strings.
[00:39:42]
And then you say, actually, if you, if you pass null to it, it has a specific meaning.
[00:39:47]
And then you can keep all of the previous code compiling, but also accept a new type
[00:39:53]
that didn't compile before because it's a proper union.
[00:39:56]
Whereas in Elm, if you, if you were to change that suddenly all the code that was passing
[00:40:01]
in strings, now it has to be just string.
[00:40:04]
So everything has to put a tag in front of it now.
[00:40:07]
So I think that's like a pretty cool feature, but, but that's that's definitely one thing
[00:40:11]
to, to, you know, really try to understand is, is how much TypeScript is fitting into
[00:40:18]
runtime checks that you could do in JavaScript.
[00:40:20]
And actually it does do in JavaScript.
[00:40:23]
And then it's using control flow analysis to narrow the types within that, which is
[00:40:28]
very pragmatic and really, really neat.
[00:40:31]
Yeah.
[00:40:32]
I don't know if I like the string or null in practice.
[00:40:36]
Yeah.
[00:40:37]
But I do think I do like the string or number or string or username, which I think is, I
[00:40:44]
think it's pretty neat.
[00:40:46]
As long as it compiles as a proper checks afterwards, which I imagine it does.
[00:40:50]
Yeah.
[00:40:51]
Well, be sure that you have strict null checks on that's another tsconfig.json option, which
[00:40:57]
I highly recommend just using strict mode.
[00:40:59]
And yeah.
[00:41:02]
Do you want to talk about nulls and then undefined in TypeScript?
[00:41:06]
Oh man.
[00:41:08]
Null and undefined.
[00:41:09]
Is it that bad?
[00:41:12]
Well, it always just throws me off how there are these two different ways to represent
[00:41:18]
this thing, but yeah, I don't know.
[00:41:20]
I don't know.
[00:41:21]
I don't know if I have that much to say about it other than why, why do we have two different
[00:41:28]
ways to represent absence of values?
[00:41:34]
My, what I always find interesting with JavaScript, not TypeScript, JavaScript is that zero divided
[00:41:40]
by zero is not a number.
[00:41:43]
It's an N. But if you look at what the mathematicians say, they say it's undefined.
[00:41:49]
Even in JavaScript, like the only language that has undefined, but even there it isn't
[00:41:57]
undefined.
[00:41:58]
It's just N.
[00:42:00]
If you're a mathematician and you go to write some JavaScript code, let's just say you're
[00:42:05]
probably going to be pretty confused.
[00:42:08]
You'll be like using an equals operator to like change the values of things.
[00:42:14]
And you're like, hmm, I don't think that's what equals means.
[00:42:18]
And then it's like, oh, are you, are you thinking of triple equals?
[00:42:21]
It's like, what is triple equals?
[00:42:24]
Wait, triple?
[00:42:25]
Are there no double equals?
[00:42:28]
Oh, we don't use that.
[00:42:31]
Yeah, no, that's rubbish.
[00:42:35]
Until how many equal signs does it go?
[00:42:38]
Five, six?
[00:42:39]
Yeah.
[00:42:40]
We use linting checks to disable double equals.
[00:42:43]
It's a bug if you use double equals.
[00:42:45]
Do you say something like equal with an exponent?
[00:42:48]
Like equals to the power of three to say triple equals?
[00:42:52]
Or three equal?
[00:42:56]
What I'm wondering about, and maybe this is just a simple lookup documentation type of
[00:43:03]
question, but if you say something like string or null, does a null mean it's the null value?
[00:43:10]
Yes.
[00:43:11]
Or it is null or undefined?
[00:43:13]
It's null.
[00:43:14]
Okay.
[00:43:15]
That I find pretty weird because a lot of things, and I see null everywhere in TypeScript,
[00:43:23]
but I also know that undefined is used everywhere because you're missing arguments or stuff
[00:43:29]
like that.
[00:43:30]
Right.
[00:43:31]
Missing arguments, you look something up in an object and it doesn't contain the key.
[00:43:35]
But then you get undefined, you don't get null.
[00:43:38]
Correct.
[00:43:39]
So does TypeScript tell you about those?
[00:43:40]
Like, oh, this is probably null.
[00:43:42]
That's a good question.
[00:43:43]
I mean, for all intents and purposes, they're going to be similar because a lot of the time
[00:43:48]
you're going to say, I mean, if you do, a lot of the time you're going to say if value
[00:43:54]
then, and so it doesn't matter because it's checking truthiness, not strict equality.
[00:44:03]
Poor number zero.
[00:44:04]
Oh my gosh.
[00:44:05]
Poor empty string.
[00:44:06]
It makes me so happy writing Elm and not having to think about which ones evaluate to false
[00:44:15]
and which ones evaluate to true.
[00:44:17]
I will never trust myself with those things.
[00:44:19]
I'll never trust myself with those things and I will never trust myself with precedents
[00:44:25]
of or and end and things like that.
[00:44:27]
Like in Elm, I always write it with parentheses and then let Elm format say, you don't need
[00:44:34]
those there.
[00:44:35]
The precedence rules will take care of that.
[00:44:37]
I'm like, okay, but at least I saw you do that and I knew what the precedence was before
[00:44:42]
I hit save.
[00:44:43]
I do the same thing.
[00:44:46]
So TypeScript also has the void type.
[00:44:52]
We don't because that is absolutely useless.
[00:44:58]
So void means, hey, I'm going to do a side effects.
[00:45:01]
Exactly.
[00:45:02]
There are no side effects in Elm.
[00:45:03]
I think void is short for avoid.
[00:45:05]
I'm fine with it.
[00:45:10]
I needed one pun in the show.
[00:45:13]
Come on.
[00:45:14]
What I find interesting, but also entirely normal because of the soundness argument that
[00:45:25]
we talked about before, is that there are no types for exceptions and there are no types
[00:45:32]
for experimentations.
[00:45:34]
So there's a lot of things that you do in TypeScript that do not reflect reality.
[00:45:40]
I feel like exceptions could have been documented, could have been annotated, at least somehow
[00:45:48]
to say, hey, I can guarantee you that this will give you an exception and this will be
[00:45:54]
the type.
[00:45:57]
I think that would be really cool too.
[00:45:59]
I think it's probably just a pragmatic choice that there are so many places where exceptions
[00:46:08]
throw the same type of error or exceptions throw a string or the place that throws an
[00:46:15]
exception is coming from a library that doesn't have typings.
[00:46:22]
But Java has this notion of handled or checked exceptions and then there's also runtime exceptions.
[00:46:29]
So it's kind of weird because you get this specific set of types of exceptions that can
[00:46:38]
be thrown and exceptions that are handled.
[00:46:43]
That's cool, but then you throw something that's a subtype of runtime exception and
[00:46:49]
it doesn't have to tell you about that.
[00:46:51]
Because the idea I guess is like, well, these are things that are unexpected and unrecoverable
[00:46:57]
or something like that.
[00:46:58]
So it wanted to have an escape patch to be able to say, hey, if I'm trying to read a
[00:47:03]
file and the hard drive isn't working, I don't want to make every user handle that state.
[00:47:12]
But I don't know.
[00:47:13]
I mean, yeah, I think it's really nice that Elm lets you be so explicit that they're just
[00:47:19]
types.
[00:47:20]
And if you don't want to handle something, then you at least have to be explicit in doing
[00:47:25]
a catchall or whatever you want to do to pass that through.
[00:47:28]
But yeah, I mean, TypeScript made the choice to not have a notion of sort of checked exceptions.
[00:47:34]
And I agree, I think it would be cool.
[00:47:37]
It would be a nice feature, but I'm not sure if there are any plans for something like
[00:47:41]
that.
[00:47:42]
But so that is another blind spot of the TypeScript type system.
[00:47:48]
Is it in your blog post?
[00:47:50]
It is.
[00:47:51]
Okay, good.
[00:47:52]
You did a good job, Dillon.
[00:47:53]
It's it.
[00:47:54]
It's my catalog of places you can introduce unsoundness.
[00:47:59]
If you find something that's not in there, please let me know because I'll try to keep
[00:48:04]
it up to date with any other things people think of.
[00:48:07]
So okay, so before we finish, I think it would be nice to maybe we can go through this catalog.
[00:48:15]
But one more thing I wanted to touch on is like this difference in the Elm and TypeScript
[00:48:20]
type systems of custom types with variants in Elm.
[00:48:25]
Yeah, we should talk about those.
[00:48:28]
And discriminated unions in TypeScript.
[00:48:30]
So discriminated unions are very cool.
[00:48:32]
And I think it's important to realize that as far as just the feature of a discriminated
[00:48:39]
union goes, it's a pretty sound feature.
[00:48:43]
Can you explain what those are?
[00:48:44]
Yeah, absolutely.
[00:48:45]
So now again, if you want to understand the TypeScript type system, think about the the
[00:48:53]
motivation and design constraints of the author.
[00:48:55]
So similar to type of string checks, it uses narrowing using kind of code flow analysis.
[00:49:04]
Same with discriminated union.
[00:49:05]
Dillon, you sound like a broken object.
[00:49:07]
That's a good one you're in.
[00:49:12]
And that might have to go on a t shirt.
[00:49:16]
Sounds good to me.
[00:49:19]
But yeah, so it's using the same technique of this sort of runtime analysis check that
[00:49:26]
it hooks into to narrow the types to give you type information in that context of the
[00:49:32]
control flow.
[00:49:33]
And so if you have a field in an object, which can be, you know, a shape has a, you know,
[00:49:43]
you have an object, which can can either be shape is square, and then it has a width field
[00:49:52]
in it.
[00:49:53]
Or, so this would be like a union type using the pipe operator.
[00:49:57]
So you could say like type, shape equals, and then an object, shape, colon, square,
[00:50:05]
the string square.
[00:50:06]
So it's that's actually a string that type is a string.
[00:50:09]
And then and then you can have width is a number.
[00:50:13]
And then you could have or pipe, it's a an object shape, colon, string rectangle, and
[00:50:20]
it has the property with number, height number.
[00:50:25]
So now, what TypeScript is going to do is if you have a value of that type, you can
[00:50:30]
do like a switch statement, or an if statement or whatever, right?
[00:50:34]
Because it's just you're just doing a runtime check like you would if you actually had those
[00:50:38]
values.
[00:50:39]
So you would say, you know, if shape dot, well, shape dot shape equals square, then
[00:50:47]
you know that it must have based on the type definition, you know that it has a width and
[00:50:52]
it doesn't have a height.
[00:50:54]
And if it's a rectangle, you know, it has a width and a height.
[00:50:57]
And if it's a circle, you know, it has a radius or whatever it may be, right?
[00:51:01]
So this is how unions work.
[00:51:03]
TypeScript has this notion of like literal values, which is a really cool feature.
[00:51:08]
So like a literal value, you could have a type string, but you could have a, you know,
[00:51:14]
subsets.
[00:51:15]
You could have a sub.
[00:51:16]
Exactly.
[00:51:17]
So you could have like a mode argument to something, you could have a parameter that's
[00:51:21]
mode.
[00:51:23]
And mode is a string and well, what do you pass it?
[00:51:25]
You say mode is either verbose or silent or something like that, right?
[00:51:31]
And well, if you know the specific subset of strings that it could be, then you would
[00:51:37]
just say mode is either verbose or silent and you could add different things to it.
[00:51:42]
But that's the literal string.
[00:51:44]
It's not a, you know, magical variant type like in Elmore, it's the specific thing.
[00:51:49]
It's just a string, but you're saying this can only be these strings.
[00:51:53]
And again, using this sort of control flow analysis, TypeScript can say, Hey, you're
[00:51:58]
passing a literal string and you're passing the string, you know, loud instead of verbose.
[00:52:04]
And it'll say, Hey, you can only pass verbose or silent here.
[00:52:07]
So this string is incorrect.
[00:52:09]
So it it's actually analyzing the values of your code.
[00:52:12]
Whereas in Elm, you would have to define a custom type, which you could not reuse.
[00:52:18]
It would be only for those places.
[00:52:20]
Right.
[00:52:21]
It's like a nominal type.
[00:52:23]
It's a nominal tape and that works well, but it's also sometimes quite annoying.
[00:52:29]
Right.
[00:52:30]
And so I do like that part about TypeScript.
[00:52:33]
It's very pragmatic.
[00:52:34]
It's a very, very nice feature.
[00:52:37]
And you know, and if you, well, I'm not sure it's pragmatic.
[00:52:41]
I think it's just a good feature.
[00:52:42]
I mean, they didn't have to be.
[00:52:44]
Absolutely.
[00:52:45]
Yeah.
[00:52:46]
I mean, in a way I think they did because TypeScript, you know, in a way TypeScript
[00:52:51]
is almost like descriptive rather than prescriptive.
[00:52:54]
It's like TypeScript is a type system to, to attempt to describe how people use JavaScript,
[00:53:01]
not how people should write code.
[00:53:03]
And that's what people, people do.
[00:53:05]
They pass literal strings and then they check if the string is, you know, verbose or if
[00:53:11]
the string is silent, then I'm going to change the behavior.
[00:53:14]
Sure.
[00:53:15]
And people can still pass in anything else in JavaScript.
[00:53:19]
So I find that the fact that they can specify a subset is just an additional feature that
[00:53:26]
they met, that they made.
[00:53:27]
And that is awesome.
[00:53:28]
They don't do the same thing for numbers as far as I'm aware.
[00:53:31]
You can't say, oh, this number is one or two.
[00:53:35]
Right.
[00:53:36]
Right.
[00:53:37]
Or I guess maybe you can with EGEM.
[00:53:38]
Actually, I think you can.
[00:53:39]
Is that what you've done so far?
[00:53:40]
Partially?
[00:53:41]
I think you can do that.
[00:53:44]
All right.
[00:53:45]
And well, that's awesome.
[00:53:46]
I wonder how they do that with like concatenations and additions, subtractions, how they can
[00:53:53]
tell whether something is indeed within the limited ranges or limited sets of values.
[00:54:01]
Well it's weird with Java.
[00:54:03]
So I just tried doing a literal type to say like, you know, type version equals one or
[00:54:11]
two and you can do that.
[00:54:13]
You can absolutely do that.
[00:54:14]
And then if you say, you know, let version of type version equal one or equal two, it's
[00:54:22]
fine.
[00:54:23]
And if you say it equals three, it says that's not of type version.
[00:54:26]
All right.
[00:54:27]
Nice.
[00:54:28]
The strange thing is you can do that with like floats, which float equality should not
[00:54:34]
be trusted.
[00:54:35]
So anyway, that's a little bit odd and probably something you should be skeptical of.
[00:54:41]
But doing it with integers, which yes, they're all just numbers, but if you only use integers,
[00:54:46]
then that's safe.
[00:54:47]
But yeah, and I think enums under the hood are just using integers.
[00:54:52]
Yeah.
[00:54:53]
And I think you can assign them other values than just the order.
[00:54:56]
Right.
[00:54:57]
Right.
[00:54:58]
So yeah, you actually can, I think.
[00:55:00]
Yeah.
[00:55:01]
That's pretty cool.
[00:55:02]
Yeah, it's a very cool feature.
[00:55:03]
So discriminated unions are just taking advantage of that notion of literals, but they're saying,
[00:55:10]
well, this object has a key, this discriminant, this key that can tell me what the shape is,
[00:55:17]
because if this key shape is square, then I know what fields and of what types it has.
[00:55:23]
If it's circle, then I know what fields and what types it has.
[00:55:27]
So it's just using that same technique of sort of type narrowing using conditional flow
[00:55:33]
analysis.
[00:55:34]
And it's just leveraging that along with like this notion of literal types where you can
[00:55:37]
say shape square, the string square.
[00:55:42]
And you just and all you have to do is if you if you check if, you know, shape dot shape,
[00:55:49]
triple equals square string square.
[00:55:52]
Now you've narrowed down that type because it has enough information to infer some things
[00:55:57]
about the type based on that.
[00:55:58]
So that's how discriminated types work.
[00:55:59]
And it's a very cool feature.
[00:56:01]
So the one thing that's very, very different in this regard between element and TypeScript
[00:56:09]
is like, if you're just using a plain old discriminated type, it works quite well, you
[00:56:15]
know, it's, it works pretty much like you would expect as an Elm developer.
[00:56:20]
But then when you when you want to create an opaque type, that's when things get very
[00:56:25]
different.
[00:56:26]
I don't want to I don't want to say anything that's that's no longer accurate, because
[00:56:33]
I know that there are some changes, like with regard to like private methods, and that sort
[00:56:39]
of thing lately.
[00:56:40]
So and I'm not familiar with the latest in that.
[00:56:42]
So things may change in this regard.
[00:56:45]
I also know there's like a feature called branded types.
[00:56:48]
And to be honest, I've tried to try to use just use them and I don't understand them.
[00:56:55]
I haven't been able to figure it out.
[00:56:57]
So if you figure it out, try it and like, let me know how it works.
[00:57:02]
Yeah, it works.
[00:57:04]
From what I understand, like a branded type allows you to, to do something similar to
[00:57:11]
an opaque type in Elm.
[00:57:14]
But all I can all I can say really is that in my personal experience, branded, I've never
[00:57:20]
seen a branded type in actual use.
[00:57:23]
I'm sure there are some libraries that that use it.
[00:57:26]
But it like, practically, I don't think people people use branded types on a day to day basis.
[00:57:32]
Whereas in Elm, people use opaque types.
[00:57:34]
So that's, that's all I feel I can safely say I think a feature may exist to get the
[00:57:39]
same type of functionality as an opaque type.
[00:57:42]
In practice, I don't think it's very common.
[00:57:44]
That's something that I think is huge.
[00:57:46]
I can't overstate the importance of opaque types as far as like, because soundness is
[00:57:51]
one thing, but protecting invariants is actually a separate thing.
[00:57:57]
It's actually not related to soundness.
[00:57:59]
And like protecting invariants is more about controlling the internals of an API.
[00:58:04]
So you know, the only thing that can refer to this is in this one file, I can't touch
[00:58:09]
it outside of there.
[00:58:11]
And this is huge in Elm.
[00:58:13]
And if you're not, you know, if you're not doing this on a regular basis, check out our
[00:58:19]
episode on opaque types.
[00:58:21]
We talk about all the details of that.
[00:58:23]
I think it's a really important technique in Elm.
[00:58:26]
I think it's underused or overly difficult in TypeScript.
[00:58:31]
On a different topic, I should say one last thing about discriminated unions.
[00:58:35]
By default, switch statements in TypeScript are not exhaustive and they will not give
[00:58:40]
you a warrant, an error of any kind.
[00:58:43]
If you have a non exhaustive case statement, switch statement, there are, there's an ESLint
[00:58:49]
rule that I'll link to in the show notes for how to prevent inexhaustive switch statements.
[00:58:56]
So you should definitely do that to make TypeScript more safe.
[00:58:59]
I think that's really crucial.
[00:59:00]
There is like a trick you can use.
[00:59:02]
There's a never type in TypeScript, which is pretty much the same as with Elm.
[00:59:06]
And you can assert that something is unreachable by returning a never type.
[00:59:13]
So you can do default and then return something that returns never type.
[00:59:18]
And it'll say, wait a minute, you can't return a never type.
[00:59:21]
But if you can never reach there, then it won't complain.
[00:59:26]
Exactly.
[00:59:27]
If you do an exhaustive case and then you say default, return this unreachable never
[00:59:32]
type, then it'll say, well, okay, the default is never going to happen because you handled
[00:59:37]
every case.
[00:59:38]
But that only works as well as you remember to do that.
[00:59:42]
And I wouldn't trust that myself.
[00:59:44]
I would want it to have a blanket error anytime I do an inexhaustive switch statement.
[00:59:50]
So there are a few things that I find interesting.
[00:59:54]
I haven't played with those in TypeScript.
[00:59:57]
But you can kind of do dynamic typing in a sense like these types will depend on other
[01:00:05]
types.
[01:00:06]
Exactly.
[01:00:07]
Perhaps dependent typing, if you will.
[01:00:09]
No, no.
[01:00:11]
Don't confuse people.
[01:00:12]
Don't confuse people.
[01:00:13]
It's not dependent types.
[01:00:14]
I thought people could depend on you.
[01:00:20]
It depends.
[01:00:27]
Ever since you started being an independent worker.
[01:00:30]
So yeah, you have something in TypeScript type system that are like you have key ofs
[01:00:39]
and type of where you can say, oh, this type here, that's just going to be any key of this
[01:00:48]
record.
[01:00:49]
And then TypeScript figures out things on its own.
[01:00:55]
I haven't played with those and I don't know if it's if they're interesting or if they're
[01:01:00]
foot guns, just like how because they become dependent on what other code is doing.
[01:01:09]
And I feel like we're coming back to the argument about let variables and no type annotations
[01:01:14]
over there where if something changes, I kind of want to know.
[01:01:19]
So is it kind of like laziness or is it really interesting?
[01:01:25]
I mean, I think it I can't speak to the foot guns of it too much because I haven't encountered
[01:01:33]
any.
[01:01:34]
There may be, but it certainly is a complexity in the type system.
[01:01:39]
And we've talked about this before, how like, I mean, I'll give my rant again briefly.
[01:01:44]
The reason we can trust types as like a proof about our programs is because of their simplicity.
[01:01:51]
Like the program itself is a proof to you could follow the flow and prove what it can
[01:01:56]
and can't do it.
[01:01:58]
But types give us a very simple proof that we can follow easily.
[01:02:03]
So the utility of a type is that we can, you know, our brains are able to track what the
[01:02:10]
types are because it's simple and the type system can help us enforce those simple constraints.
[01:02:18]
And we can reason about our code better that way.
[01:02:20]
So the simplicity of type systems can be one of the core features of it.
[01:02:25]
And this certainly adds complexity to the type system, which could make it harder to
[01:02:29]
follow the correctness of your program potentially.
[01:02:34]
But I know that like you can do some really interesting like metaprogramming type things
[01:02:40]
as library author using these types of features.
[01:02:44]
So there's like, there are some utility types and there are some really powerful like features
[01:02:49]
in the type system.
[01:02:50]
And I know that like the, you know, the Prisma team, for example, that have this like ORM
[01:02:56]
sort of like, you know, database invocation tool that uses TypeScript to make type safe
[01:03:03]
database queries.
[01:03:04]
They make heavy use of these advanced TypeScript features.
[01:03:08]
You know, if you like Google, Prisma, advanced TypeScript, you'll probably find some of their
[01:03:13]
like meetup talks and stuff.
[01:03:14]
I've watched some of them and they're very hard to follow.
[01:03:18]
My TypeScript foo is not advanced enough to follow the techniques they're using, but there
[01:03:24]
are some really advanced things you could do to build tools like Prisma where you can
[01:03:29]
describe the set of database queries you can make with a particular schema, which is very
[01:03:34]
cool.
[01:03:35]
Another interesting thing in TypeScript is like there's no tuple type in TypeScript.
[01:03:40]
There's just arrays.
[01:03:41]
There is?
[01:03:42]
Well, right.
[01:03:43]
They're technically, what I mean to say is the tuple type is not distinct from an array.
[01:03:47]
It's more of like a literal type.
[01:03:50]
It's like a literal of an array with two entries.
[01:03:56]
Yeah.
[01:03:57]
There's a tuple type, but there's no tuple value.
[01:04:00]
Right.
[01:04:01]
It's not a distinct special type.
[01:04:03]
It's more like a literal, just like a discriminated union.
[01:04:07]
It's not really a type.
[01:04:08]
It's like a literal and it uses these sort of type narrowing techniques with the conditional
[01:04:14]
control flow to check that.
[01:04:16]
So, yeah, you can say this is an array, but specifically it's a subset of arrays.
[01:04:21]
It's not just any array.
[01:04:22]
It's the subset of arrays that contain two values of this type, which I think is very
[01:04:27]
cool.
[01:04:28]
Whereas in Elm, a tuple is more like a nominal type.
[01:04:32]
It's kind of an in between sort of thing because it actually is like a special thing, but I
[01:04:37]
think that's a cool feature of TypeScript.
[01:04:39]
Yeah.
[01:04:40]
And there's no upper size limits for tuples in TypeScript.
[01:04:43]
Right.
[01:04:44]
For better and for worse.
[01:04:45]
Can you give me a short summary on the differences between interface and type and declare, I
[01:04:55]
think?
[01:04:56]
It is short too much to ask.
[01:05:00]
Type aliases versus interfaces.
[01:05:03]
There's like a lot of debate around which is better, which is like faster for compiler
[01:05:09]
performance, which has whatever.
[01:05:14]
I think I've heard people have this discussion and I don't think there's like a clear answer.
[01:05:21]
At least there's not a concise way to describe it.
[01:05:23]
I tend to just use type aliases because it feels more natural as an Elm developer to
[01:05:29]
use a type alias to like an object type definition.
[01:05:33]
So that would be the type keyword, right?
[01:05:35]
Yep.
[01:05:36]
Exactly right.
[01:05:37]
Yep.
[01:05:38]
And then interface.
[01:05:39]
Because, yeah, because it's like...
[01:05:43]
Is interface about objects?
[01:05:44]
Yeah, interface is like saying that it's an object with a subset of particular keys.
[01:05:49]
And then there's like strange stuff where many different types of things are actually
[01:05:52]
objects under the hood.
[01:05:54]
So you can extend the array type in TypeScript and there's all sorts of...
[01:05:59]
So personally, I just like not to think about those details and I just use type, the type
[01:06:06]
keyword.
[01:06:07]
But that's just me.
[01:06:08]
Yeah.
[01:06:09]
And that's because you don't want to use JavaScript, you want to use a typed language.
[01:06:13]
Yeah.
[01:06:14]
You don't see TypeScript as a linter, you see TypeScript as a language with types that you
[01:06:21]
can use and understand.
[01:06:22]
Yes.
[01:06:23]
And I also prefer to pretend that inheritance doesn't exist because it's very confusing
[01:06:30]
in general and particularly confusing with prototypal inheritance in JavaScript.
[01:06:36]
Yeah.
[01:06:37]
Well, I think we've covered things fairly thoroughly.
[01:06:44]
In a very systematic and organized fashion.
[01:06:48]
Yes.
[01:06:50]
So, now...
[01:06:52]
Oh, let's see.
[01:06:54]
Now definitely check out this TypeScript blind spots blog post.
[01:06:59]
I have like a few other cases to be aware of.
[01:07:03]
And again, please, truly, if there's anything else that you are aware of that introduces
[01:07:09]
unsoundness into the TypeScript type system, let me know and I will catalog it there because
[01:07:15]
I think that's just a good thing to know, where you can trust it and where you can't.
[01:07:19]
But check that out.
[01:07:21]
I've also mentioned my TypeScript without transpilation post before that kind of talks
[01:07:26]
about using js.comments to get type information.
[01:07:30]
I think that's a useful technique.
[01:07:32]
So we'll link to that blog post.
[01:07:33]
Any other good resources?
[01:07:35]
And what if I want to use Elm with TypeScript?
[01:07:37]
Oh, well, I'm glad you asked.
[01:07:41]
Elm TS Interop.
[01:07:42]
I'm actually just releasing a new version of the community edition and a new doc site
[01:07:47]
today.
[01:07:48]
So by the time you hear this, it'll already be out.
[01:07:51]
As well as a new pipeline API and codec API for the Elm TS JSON API.
[01:07:58]
So yeah, check that out.
[01:07:59]
Check out our Elm TS Interop episode.
[01:08:01]
What other resources are there?
[01:08:03]
The TypeScript documentation site has a lot of good examples.
[01:08:07]
Do you know of any TypeScript to Elm comparison files or something?
[01:08:13]
That's a good question.
[01:08:16]
There are a bunch of search results, but a lot of them are these generic stack ups type
[01:08:24]
sites that I don't find that useful because they're sort of mechanical.
[01:08:28]
But if we find a good one, we'll link to it.
[01:08:31]
Yeah.
[01:08:32]
All right.
[01:08:33]
Well, until next time.
[01:08:34]
Until next time.