¿Por qué utilizar tipos estáticos en JavaScript? (Un manual de 4 partes sobre tipificación estática con Flow)

Como desarrollador de JavaScript, puede codificar todo el día sin encontrar ningún tipo estático. Entonces, ¿por qué molestarse en aprender sobre ellos?

Bueno, resulta que los tipos de aprendizaje no son solo un ejercicio de expansión mental. Si está dispuesto a invertir algo de tiempo en aprender sobre las ventajas, desventajas y casos de uso de los tipos estáticos, podría ayudar enormemente a su programación.

¿Interesado? Bueno, estás de suerte, de eso se trata el resto de esta serie de cuatro partes.

Primero, una definición

La forma más rápida de comprender los tipos estáticos es contrastarlos con los tipos dinámicos. Un lenguaje con tipos estáticos se denomina lenguaje de tipado estático . Por otro lado, un lenguaje con tipos dinámicos se denomina lenguaje de tipado dinámico .

La diferencia principal es que los lenguajes de tipado estático realizan la verificación de tipos en tiempo de compilación , mientras que los lenguajes de tipado dinámico realizan la verificación de tipos en tiempo de ejecución .

Esto le deja un concepto más que debe abordar: ¿qué significa " verificación de tipos" ?

Para explicarlo, veamos los tipos en Java frente a Javascript.

"Tipos" se refiere al tipo de datos que se definen.

Por ejemplo, en Java, si define a booleancomo:

boolean result = true;

Este tiene un tipo correcto, porque la booleananotación coincide con el valor dado a result, a diferencia de un número entero o cualquier otra cosa.

Por otro lado, si intenta declarar:

boolean result = 123;

… Esto fallaría al compilar porque tiene un tipo incorrecto. Se marca explícitamente resultcomo a boolean, pero luego lo define como el número entero 123.

JavaScript y otros lenguajes de tipado dinámico tienen un enfoque diferente, lo que permite que el contexto establezca qué tipo de datos se están definiendo:

var result = true;

En pocas palabras: los lenguajes de tipado estático requieren que declares los tipos de datos de tus construcciones antes de poder usarlos. Los lenguajes de tipado dinámico no lo hacen. JavaScript implica el tipo de datos, mientras que Java lo establece directamente.

Como puede ver, los tipos le permiten especificar invariantes del programa , o las afirmaciones lógicas y las condiciones bajo las cuales se ejecutará el programa.

La verificación de tipo verifica y aplica que el tipo de una construcción (constante, booleana, número, variable, matriz, objeto) coincide con una invariante que ha especificado. Por ejemplo, puede especificar que "esta función siempre devuelve una cadena". Cuando se ejecuta el programa, puede asumir con seguridad que devolverá una cadena.

Las diferencias entre la verificación de tipo estática y la verificación de tipo dinámica son más importantes cuando se produce un error de tipo. En un lenguaje de tipado estático, los errores de tipo ocurren durante el paso de compilación, es decir, en tiempo de compilación. En lenguajes de tipado dinámico, los errores ocurren solo una vez que se ejecuta el programa. Es decir, en tiempo de ejecución .

Esto significa que un programa escrito en un lenguaje de tipado dinámico (como JavaScript o Python) puede compilarse incluso si contiene errores de tipo que de otra manera impedirían que el script se ejecute correctamente.

Por otro lado, si un programa escrito en un lenguaje de tipado estático (como Scala o C ++) contiene errores de tipo, no se podrá compilar hasta que los errores hayan sido corregidos.

Una nueva era de JavaScript

Debido a que JavaScript es un lenguaje de tipado dinámico, puede declarar variables, funciones, objetos y cualquier cosa sin declarar el tipo.

Conveniente, pero no siempre ideal. Es por eso que herramientas como Flow y TypeScript han intervenido recientemente para dar a los desarrolladores de JavaScript la * opción * para usar tipos estáticos.

Flow es una biblioteca de verificación de tipos estáticos de código abierto desarrollada y lanzada por Facebook que le permite agregar tipos de forma incremental a su código JavaScript.

TypeScript , por otro lado, es un superconjunto que se compila en JavaScript, aunque se siente casi como un nuevo lenguaje de tipado estático por derecho propio. Dicho esto, se ve y se siente muy similar a JavaScript y no es difícil de aprender.

En cualquier caso, cuando desee usar tipos, le indica explícitamente a la herramienta qué archivo (s) debe verificar. Para TypeScript, puede hacer esto escribiendo archivos con la .tsextensión en lugar de .js. Para Flow, incluye un comentario en la parte superior del archivo con@flow

Una vez que haya declarado que desea verificar el tipo de un archivo, puede usar su sintaxis respectiva para definir tipos. Una distinción que se debe hacer entre las dos herramientas es que Flow es un tipo "verificador" y no un compilador. TypeScript, por otro lado, es un compilador.

Realmente creo que herramientas como Flow y TypeScript presentan un cambio generacional y un avance para JavaScript.

Personalmente, he aprendido mucho usando tipos en mi día a día. Por eso espero que se una a mí en este breve y dulce viaje hacia los tipos estáticos.

El resto de esta publicación de 4 partes cubrirá:

Parte I.Una introducción rápida a la sintaxis y el lenguaje de Flow

Partes II y III. Ventajas y desventajas de los tipos estáticos (con ejemplos detallados de recorrido)

Parte IV. ¿Debería utilizar tipos estáticos en JavaScript o no?

Tenga en cuenta que elegí Flow sobre TypeScript en los ejemplos de esta publicación debido a mi familiaridad con él. Para sus propios fines, investigue y elija la herramienta adecuada para usted. TypeScript también es fantástico.

Sin más preámbulos, ¡comencemos!

Parte 1: Introducción rápida a la sintaxis y el lenguaje de Flow

Para comprender las ventajas y desventajas de los tipos estáticos, primero debe obtener una comprensión básica de la sintaxis de los tipos estáticos mediante Flow. Si nunca antes ha trabajado en un lenguaje de tipado estático, la sintaxis puede tardar un poco en acostumbrarse.

Comencemos explorando cómo agregar tipos a primitivas de JavaScript, así como construcciones como matrices, objetos, funciones, etc.

booleano

Esto describe un valor boolean(verdadero o falso) en JavaScript.

Tenga en cuenta que cuando desea especificar un tipo, la sintaxis que usa es:

número

Esto describe un número de coma flotante IEEE 754. A diferencia de muchos otros lenguajes de programación, JavaScript no define diferentes tipos de números (como enteros, puntos cortos, largos y flotantes). En cambio, los números siempre se almacenan como números de coma flotante de doble precisión. Por lo tanto, solo necesita un tipo de número para definir cualquier número.

numberincluye Infinityy NaN.

cuerda

Esto describe una cadena.

nulo

Esto describe el nulltipo de datos en JavaScript.

vacío

Esto describe el undefinedtipo de datos en JavaScript.

Tenga en cuenta que nully undefinedse tratan de manera diferente. Si intentaste hacer:

Flow arrojaría un error porque voidse supone que el tipo es de un tipo undefinedque no es el mismo que el tipo null.

Formación

Describe una matriz de JavaScript. Utiliza la sintaxis Array<; T> para describir una matriz cuyos elementos son de algún tipo T.

Observe cómo reemplacé Tcon string, lo que significa que estoy declarando messagescomo una matriz de cadenas.

Objeto

Esto describe un objeto JavaScript. Hay algunas formas diferentes de agregar tipos a objetos.

Puede agregar tipos para describir la forma de un objeto:

Puede definir objetos como mapas en los que asigna una cadena a algún valor:

También puede definir un objeto como un Objecttipo:

Este último enfoque nos permite establecer cualquier clave y valor en su objeto sin restricciones, por lo que realmente no agrega mucho valor en lo que respecta a la verificación de tipos.

ninguna

Esto puede representar literalmente cualquier tipo. El anytipo está efectivamente desmarcado, por lo que debe intentar evitar su uso a menos que sea absolutamente necesario (como cuando necesita optar por no participar en la verificación de tipos o necesita una trampilla de escape).

Una situación para la que puede resultarle anyútil es cuando utilice una biblioteca externa que amplíe los prototipos de otro sistema (como Object.prototype).

Por ejemplo, si está utilizando una biblioteca que amplía Object.prototype con una doSomethingpropiedad:

Puede recibir un error:

Para evitar esto, puede utilizar any:

Funciones

La forma más común de agregar tipos a funciones es agregar tipos a sus argumentos de entrada y (cuando sea relevante) el valor de retorno:

Incluso puede agregar tipos a funciones asíncronas (ver más abajo) y generadores:

Observe cómo nuestro segundo parámetro getPurchaseLimitestá anotado como una función que devuelve a Promise. Y amountExceedsPurchaseLimitse anota que también devuelve un Promise.

Escriba alias

El alias de tipos es una de mis formas favoritas de usar tipos estáticos. Le permiten usar tipos existentes (número, cadena, etc.) para componer nuevos tipos:

Arriba, creé un nuevo tipo llamado PaymentMethodque tiene propiedades que se componen de numbery stringtipos.

Ahora, si desea utilizar el PaymentMethodtipo, puede hacer:

También puede crear alias de tipos para cualquier primitiva envolviendo el tipo subyacente dentro de otro tipo. Por ejemplo, si desea escribir un alias Namey EmailAddress:

Al hacer esto, estás indicando que Namey Emailson cosas distintas, no solo cadenas. Dado que un nombre y un correo electrónico no son realmente intercambiables, hacerlo evita que los mezcle accidentalmente.

Genéricos

Los genéricos son una forma de abstraer los tipos mismos. ¿Qué significa esto?

Vamos a ver:

Creé una abstracción para el tipo T. Ahora puedes usar el tipo que quieras representar T. Para numberT, Tfue de tipo number. Mientras tanto, arrayTT era de tipoArray er>.

Yes, I know. It’s dizzying stuff if this is the first time you’re looking at types. I promise the “gentle” intro is almost over!

Maybe

Maybe type allows us to type annotate a potentially null or undefined value. They have the type T|void|null for some type T, meaning it is either type T or it is undefined or null. To define a maybe type, you put a question mark in front of the type definition:

Here I’m saying that message is either a string, or it’s null or undefined.

You can also use maybe to indicate that an object property will be either of some type T or undefined:

By putting the ? next to the property name for middleInitial, you can indicate that this field is optional.

Disjoint unions

This is another powerful way to model data. Disjoint unions are useful when you have a program that needs to deal with different kinds of data all at once. In other words, the shape of the data can be different based on the situation.

Extending on the PaymentMethod type from our earlier generics example, let’s say that you have an app where users can have one of three types of payment methods. In this case, you can do something like:

Then you can define your PaymentMethod type as a disjoint union with three cases.

Payment method now can only ever be one of these three shapes. The property type is the property that makes the union type “disjoint”.

You’ll see more practical examples of disjoint union types later in part II.

All right, almost done. There are a couple other features of Flow worth mentioning before concluding this intro:

1) Type inference: Flow uses type inference where possible. Type inference kicks in when the type checker can automatically deduce the data type of an expression. This helps avoid excessive annotation.

For example, you can write:

Even though this Class doesn’t have types, Flow can adequately type check it:

Here I’ve tried to define area as a string, but in the Rectangle class definition we defined width and height as numbers. So based on the function definition for area, it must be return a number. Even though I didn’t explicitly define types for the area function, Flow caught the error.

One thing to note is that the Flow maintainers recommend that if you were exporting this class definition, you’d want to add explicit type definitions to make it easier to find the cause of errors when the class is not used in a local context.

2) Dynamic type tests: What this basically means is that Flow has logic to determine what the the type of a value will be at runtime and so is able to use that knowledge when performing static analysis. They become useful in situations like when Flow throws an error but you need to convince flow that what you’re doing is right.

I won’t go into too much detail because it’s more of an advanced feature that I hope to write about separately, but if you want to learn more, it’s worth reading through the docs.

We’re done with syntax

We covered a lot of ground in one section! I hope this high-level overview has been helpful and manageable. If you’re curious to go deeper, I encourage you to dive into the well-written docs and explore.

With syntax out of the way, let’s finally get to the fun part: exploring the advantages and disadvantages of using types!

Next up: Part 2 & 3.