Thank you for being here. I was not expecting such a large room of JavaScript developers
and nothing has been broken yet, so it's unbelievable. So yeah, I'm here to talk about
strong dynamic type checking for JavaScript, which may sound a bit weird because you are
not expecting strong type checking in JavaScript in the same sentence, right? But I will prove
that we can do something about it. So first, what is strong type checking? What do we mean
by that? So let's do a bit of vocabulary. So I try to find one definition online of
what is strong type checking and couldn't find any. It's like a never-ending argument
about which one, which language is strong and which one isn't. But I found two definitions
commonly accepted. The first one is that this strong type checking means that you have this
kind of explicit binding between some variable name and some type. So this variable and this
type are like bound together. That means that every time you are calling variable by its
name, you will get some reference data that matches the type you expect. The second definition
is more regarding the program-languaging features, like no lack of type safety due to
loser-typing rules. For example, in the case of JavaScript, we have implicit type coercion.
That means that it's perfectly fine to get the plus operator between a number and a string.
For JavaScript, it's just string concatenation and automatically casting the number to a
string. But for other languages, like Python, you will get a type error. So let's take on
as an example to show that Python is more strong, stronger than JavaScript. So when
it comes to JavaScript, whatever definition you pick, you might say that JavaScript is
not a strongly typed language and you will be right. Because JavaScript is based on dynamic
type checking, dynamic typing, that means that JavaScript variables, you can assign
it to some type and then move to another type and go on. It doesn't matter. So because
types can change during program execution, that makes types in JavaScript quite unpredictable.
So the creator of the language, Prandon H, justified his choice by saying that developers
can be more expressive when you get dynamic typing, which means that it can get to the
result faster, but he also agrees that it is also more error-prone. So that's the image
I took for static versus dynamic typing. I think it sums up pretty well. So yeah, JavaScript
and strong type checking, not really. Actually, every time you see someone complaining about
JavaScript or mocking JavaScript, it would be about one of these memes, right? So these
are some of my favorites. Maybe you know others. But as you can see, almost all these jokes
about JavaScript are basically about the lack of strong type checking, which is too bad.
So some people will decide to just get rid of JavaScript and maybe go to Kotlin or.NET
or whatever language we have seen this morning. But I think that's the most common solution
to this approach has been TypeScript, right? I mean, this thing has been invented specially
to address this issue by having some optional static type checking about JavaScript. So how
many of you are using TypeScript? Raise your hand. Wow. I was expecting that. Like, almost
100% of these people in this room are using TypeScript. I mean, why wouldn't we use TypeScript?
It's so popular. Almost all the ecosystems, all the libraries today on VNPM ecosystem
have been converted to TypeScript or provide TypeScript definition files. So we have seen
this kind of exponential growth in popularity among the years. TypeScript is 10 years now.
It will be 11 in five days, actually. And it has never been so popular. So did we solve
the issue of type checking JavaScript with TypeScript? I would say not entirely and I
explain why. Here are some things I learned about TypeScript after 110 years. The first
one is a bit of shock is that TypeShaking is not actually the main selling point of TypeScript.
The main selling point of TypeScript is developer experience. So if you have practiced TypeScript
and the whole room has done that, it's great. You have seen some improvements in your development
experience. So many things like be able to explore some API by using the autocomplete,
be able to detect some typos that you have done in your code, be able to refactor your
code more easily thanks to the static type annotations, maybe have some documentation
right inside your IDE. Some compilers are using static type annotations to bring some
compile time optimizations, which is great. You get type inference, good type inference
in TypeScript, so you don't have to write all the static type annotations at any time.
And we have seen some innovative use of TypeScript. For example, the Angular community is using
a lot of TypeScript annotations to make some code generation, which is great. So all of
this is part of developer experience and is great, but it's not really about type checking
anymore. It's much more than that. I figured out that type checking was not the main selling
point of TypeScript or at least not anymore. When I looked at the ES build project, one
of the most important JavaScript project of these last years, ES build, the famous bundler,
so maybe you are using the VIT development server, some people in the room. So VIT is
based on ES build. And does ES build support TypeScript? Of course it does. Everyone is
using TypeScript. But the fact is that ES build does not actually do any type checking when
it compiles some TypeScript code. All it does is look at the TypeScript code, look at the
TypeScript part of the static type annotations, and just get rid of it. That's all it does.
Nothing else. And they say that running the whole TypeScript compiler is actually a loss
of time today. Because of this development experience, developers have this whole integrated
type safe environment and development process. So that means that you don't need to do it
twice a second time on compilation. The second point of TypeScript that I learned
about 10 years later, it's that type safety in TypeScript can be easily defeated. What
I mean by that is that in many scenarios in your application, you are relying on type
assertions. That is these little elements like the ASCII word or the exclamation mark
here, which is I ensure you the compiler that is not null. So all these things are not bringing
any type safety. It's just the developer saying to the compiler, trust me, I know what I'm
doing. And most of the time, we do not. So yeah, this problem, these type assertions,
you can find them easily on any web application. There are actually many parts where you are
forced to use these kind of assertions because of the nature of the web. You can have your
perfectly type safe TypeScript application and still have to deal with lots of unpredictability.
Unpredictable is like the most of your time, your job for a front-end web developer. And
what I mean by unpredictable, it is known at runtime. That means it changes every time
at every user and so on. So for example, your application may have some back-end services,
may call some APIs, maybe some third-party cloud providers. And you are trusting the
responses of these servers, right? You are not validating any of the response of the
server from the application side. So this could break. You are also relying on a browser.
And some browsers have bugs and queers. They do not fully support visual script APIs. The
web standard APIs. I chose this logo for a reason, why? You may also have some client-side
store data for your application. Maybe you are storing on a local storage some user preferences
or some five-store age users' cache. So this is likely to break as well because sometimes
the cache is outdated. It comes from an older version of your application or maybe it has
been modified by the user itself, who knows. And finally, maybe the most unpredictable
part of every developer's job, did you guess it? The user. The user can be very unpredictable.
If you have some application in production and have a look at the production database,
you will always find some crazy stuff like, how did it get there? I don't understand.
This is the user. All these things need to be validated. Otherwise, this is a recipe
for disaster and can break your perfectly safe application in TypeScript. So if you look
at TypeScript and wonder, how can I do that? How can I type check all of these things? No
luck. It's not a compiler problem because all these things happen at runtime. You cannot
anticipate it. So it's more an applicative problem and not a compiler problem, which
means that TypeScript is completely helpless and it is up to you, the developer, to find
a solution to these problems. So how do we deal with runtime errors? Most
of the time, the truth is that often we don't. Maybe in the best of scenarios, you are doing
some custom logic to validate the data and trying to fix the stuff the best you can.
But most of the time, you have so many different possible runtime errors that you would have
like try catch blocks and trying to show some error messages to the user, saying them to
call you and send us an email in case something bad happens. And I also saw that we have some
kind of global unexpected exception handler that is just sending all the runtime errors
that you didn't catch to maybe a monitoring service. And it is added to a log file that
you are checking like once in a month, looking at a bunch of errors and saying it's not worth
my time, so I should move to something else. I don't know if some of you do that, but it
happens, right? So it's too bad because we could figure it, figure a way to solve all
these runtime errors. So back to this idea of strong dynamic type checking. How can we
do that? So I'm just taking to the definition of a strong binding between variable name
and a time here. What if we could do this kind of strong binding but at runtime? What
would it mean? First, it would mean that the type errors that we get would still be runtime
errors, right? Because it happens at runtime. But at least there will be more explicit and
more catch early. That means that instead of having like undefined is not a function,
you will get an error message like this variable has been from undefined and it was not supposed
to. So instead of pointing to the consequences, it points to the source of the problem. So
that helps a lot to reduce the investigation job that you have to do as a developer when
doing debugging. The second thing is that this strong binding, it should not be just
a one-time validation pass. I'm sure that there are plenty of JavaScript libraries that
do that. That is, you are throwing a bunch of data to it and it validates, saying true
or false or just throwing a type error. But we need more than that. We actually need to
have this binding. That means the type information needs to live along with your data. So it
should be validated, this type checking thing, on every reassignment or mutation of this
data. And finally, the goal of this is to get rid of maybe some silent errors because
we have many mistakes in JavaScript that just are silent. That is, you are not noticing
them until it's too late. And it can also make runtime errors maybe more predictable
and so more manageable from a developer's point of view. So this is the main reasoning
I have when I worked on the open source library that I want to present you today, which is
object model. So definitely not a new project. Actually, I've been working on this for the
past eight years. So I'm at the version of 4.4.1. That means that I have written the
entire thing like four times now. It's obviously the hardest thing I had to code in my life,
I would say. It's very complicated, but it works. So I'm glad. And I would say also that
it is my most used for real open source project. By use for real, I mean that it is used in
business projects. So I use it in my professional project. Other people are using it as a fundamental
component of their business project and I receive lots of positive feedback about this
library. You've got an example here. So what is this library doing? So how do you use it?
It's pretty simple actually. The first thing you have to do is define the dynamic types,
I would say, I would call them models. I explain the difference later. But basically, let's
say you are working on e-commerce application. You can declare an object model for the order,
for example, the customer order, saying that you have a product which has a name property
which is a string, a quantity that is a number, and also an order date. After I've been declared
this model, you can now bind it to some data. So this is where you have this strong binding
between the type and the variable. Here, I used the constructor pattern, so that means
you are calling new order. I think it's probably the most intuitive form of a binding for the
developer. And also, it helps to store the type information on the prototype level of
the object. So that's how I have this strong binding. We already have a binding between
objects and prototypes in JavaScript. And after having done that, you get the myorder
object, which you can manipulate just like you would do with a regular JavaScript object.
But instead, when you are assigning, for example, quantity to Boolean instead of a number, you
will get a dynamic type error at runtime with an explicit message saying, expecting product
quantity to be number and got Boolean false instead. So because this happens, every time
you are doing any mutation on an object, it is really easy to quickly find some mistake
that you are doing as a developer and so improve the debugging experience.
So that's great, but how does it work? So let's start from a pretty basic example. Here,
I have a class user having a constructor taking a name and an age. And if you want to validate
the parameters that are passed to a function and not rely on static type annotation in
JavaScript, that means that you would validate this data at runtime. What you could do is
use these if conditions and check the type of these different variables and throw type
errors like that. Pretty easy. The problem with that is that it only works in the constructor.
So maybe you could decide to declare some setters like set name, set age, and have this
validation process on every single attribute, but it's a bit tedious and we can do better
on that. So we can improve this by using a feature of JavaScript, which is the proxy.
So I don't know if everyone knows about proxies. This is a feature of JavaScript that has been
introduced in 2015 as part of Xmascript 6. And proxies are actually really great features,
really powerful. The way proxy works is that they enable you to intercept some operations
that are done on some object and react to these operations. So in this example, I just
use the set trap of the proxy, which means that every time I am reassigning a property,
I can execute some custom logic. So I can move my if type of name, different string
and so on into the set trap and be able to detect the different issues. So that's great.
So it works both for the constructor and the future reassignment, the future mutations.
What we can do as a first step is try to make a generic function out of this. Like so.
So now I just move the definition part on this generic type check function argument.
So the type check function takes two arguments. First is the data. Second one is the definition
or the type if you prefer. So it makes clear that you have this strong binding between
objects and types. And as you can see, it is really easy to make a generic function to
do this kind of stuff. So the type check function that you see here is a very basic version
of what object model is. Of course, the library is much more complicated than that. It can
cover many other use cases, but you get the idea with this example. So as you can see,
it is really easy to reuse this type check function to apply to many different models.
So why did I call these models and not types? Actually, I wanted to find another word just
to make straight that there is a few differences from the types that you know from TypeScript,
for example, because everything happens at runtime. This is runtime data validations.
That means that models are more powerful than just the static types. For example, they can
not only check the types, but also the values. Let's say I have a short model which can have
a size which is either a number or a letter like MSL, Excel, and so on. I could decide
to have this kind of type annotation to have both control that it is either a number or
a letter M or a string matching this regular expression. I can also have some more complex
assertions. For example, if I want integers in JavaScript, yeah, integers in JavaScript.
So it will be the number in the end because that's how JavaScript handles numbers. It's
double 64 bits. But I can add another assertion on this number model to say I need to check
that it is an integer. And maybe if I want it to be a positive integer, I can add another
assertion to make sure that it's above zero. So this is the kind of stuff of assertions
that you can have. And again, every time you are manipulating this property, for example,
the age of the user, it will check all these assertions automatically. And also the last
difference from models to types is that model validation can affect application logic. Because
it's happening at runtime, that means you need to react to it and have some strategies
of how to handle these runtime errors. For example, if you got some error, some type error
on your short model, maybe you just want to cancel the order so that you are making sure
that everything is happening correctly on your application. So these are the main differences.
So to get a look at the pros and cons of this library, first the pros. You get descriptive
error messages. They are pointing to the initial problem, not the consequences. So just that
saves you a lot of time. And it means that you now have this kind of continuous data
validation as part of your development process. That means you get faster debugging and more
reliable applications in the end. Regarding how you manage these runtime errors, because
you need to do something, right? Not only showing an error message, but maybe doing
some strategies that are planned. You can define some global or maybe per feature, per
model strategies about how to manage these errors. Maybe some errors can be easily manageable.
For example, clean up an outdated cache. Or maybe some of them are more complex and then
you need to maybe log them into a monitoring service. Some of the cons of this library,
one about performance, of course, because since it's happening at runtime, that means
that it has a cost, a performance cost. Don't worry, it's not too much. But if you are doing
some heavy mutations, like more than 100 times per second, maybe you should avoid using dynamic
type checking for this specific scenario. But most of the times you don't have to do
this, so it's great. The second problem is that it relies on proxies. So you need support
for it. Today, modern browsers are supporting ES6 proxies very well. But if you have older
browsers for some users, this can be an issue. So, which is better? Static type checking
or dynamic type checking? The correct answer is you should use both because they address
different issues. Type script, we saw it. It's awesome. It improves a lot with the developer
experience. It makes you have a coding base which is reliable and makes sense, which is
logical. But you should also take care of all the unpredictable parts that are happening
at the runtime of your application. So my personal recommendation would be to stick
to TypeScript for the core application logic but also add this object model layer for every
external interface that you have to deal with, like the server, user inputs, the local stores
or the browser APIs. And this can lead to a more reliable application. That's all I
have for you today. So thank you for listening and I'm taking questions.
Thank you very much. So, we have time for questions. Who would like to ask the first
question? My question is, have you ever tried using this library with other libraries for
like, as it called, immutable data or for other validation? Have you tried using this library
with other libraries for dynamic checking like YAP or for immutable data like EMR or other
libraries? Yeah, so immutable should work fine. For other validation libraries, I mean it's
kind of the same thing, the same job, so maybe it doesn't work that well and doesn't make
really sense. But I think it should work perfectly with immutable data structures. So EMR should
work fine. Yeah.
Hi. Do you think it would be possible to generate the object model, like definitions I don't
know what's called, object models from TypeScript?
Okay, that's a good question. So actually, you can do the opposite. That means that if
you are using models, it will generate TypeScript types for you. But because this is more than
type checking and as you saw, this can affect application logic. That means that we cannot
do this simple conversion. If you use it dynamic type checking, just like you would do with
TypeScript types, you are just using like 10% of the potential of a library and it wouldn't
make any sense to me. So you should see the website maybe to have more example of that,
but yeah, it's a bit more than that.
Yeah, I see hands. You were there. The next speaker also, could you raise your hand or
stand up? Okay, so we'll have to contact them.
A fantastic idea. Love the library. You mentioned rewriting it four times over eight years.
How stable would you consider the project to be? How often do breaking changes to the
API get introduced, that kind of thing?
Yeah, it's true that I've written it four times, but the API never changed. That's one
thing. And also I use it for professional projects. So I would be embarrassed if I had
to throw it away, all right? So it's quite stable for many years now.
Hello. Thank you for the presentation. And I would like to know whether would you recommend
using object model on projects that has not yet TypeScript, only JavaScript. Thank you.
Yeah, I mean, that could be a thing. Although if you are into strong type checking, you're
probably already using TypeScript. If it's not the case, maybe it's fine. I don't know.
But yeah, it's totally possible. But most of people are using TypeScript.
Thank you very much. We have time still if you have time for another
question. Yeah, you'll have to be loud because people are moving. And if you sit down, please
make sure there is no space because the room is pretty full. Hi. Thank you for your project.
So one other approach is using, for example, JSON schemas that then translates to types,
should I speak? Sorry, I can't hear you.
One other approach is using JSON schemas, for example, on the validation side, let's
say, in a controller. And the JSON schema then compiles to the or deduces the TypeScript
type that the schema defined. So that's one way, for example, to do validation and not
have a runtime penalty besides doing the validation itself. Have you considered this approach
for your use case? Yeah, so good question. So you can indeed use
this kind of type declaration. One problem is that, again, if you are sticking to what
can TypeScript do with static type checking, you are only just using a fraction of what
can be done. For example, I told you about custom assertions. I told you about the fact
that you can check values along with the types. So all of these things would not match the
model that you are describing with JSON. So that means we need to have another API for
that, and that's why I have the on API for object model.
Another last question, and then please, everybody, squeeze no empty seat. There are a lot of
people still standing. So JavaScript is executed with V8, and there
is a lot of optimization underneath where you have an inlining going on optimization
of the function. When you use proxies, all of that is going to be gone immediately. Like
the performance set is not only when you set something and when you would normally go through
the proxy. It's not a hook. How's it called again? I forgot the name. Anyhow. Like when
you have the trigger for it, but also for anything that relies upon that data from that
point on. So it's going to be a huge performance. I would definitely not recommend this pretty
much in production.
Yeah, I talked about the performance issues. One thing is that it's only useful for applying
to external interfaces like network request. You can just validate everything related to
one request. So the loss of time due to the network request compared to the loss of time
due to proxy, it's acceptable in my opinion.
You can debate after. Don't worry. In his go.
I mean, I run a bunch of assertions and it takes less than 10 milliseconds. So I don't
think the loss of time is so much trouble. Thank you very much.
Thanks again.