¿Por qué TypeScript es una mejor opción que JavaScript cuando se trata de programación funcional?


En esta publicación, me gustaría discutir la importancia de los tipos estáticos en los lenguajes de programación funcionales y por qué TypeScript es una mejor opción que JavaScript cuando se trata de programación funcional debido a la falta de un sistema de tipos estáticos en JavaScript.

dibujo

La vida sin tipos en una base de código de programación funcional #

Intente pensar en una situación hipotética para que podamos mostrar el valor de los tipos estáticos. Imaginemos que está escribiendo código para una aplicación relacionada con las elecciones. Acabas de unirte al equipo y la aplicación es bastante grande. Debe escribir una nueva característica, y uno de los requisitos es garantizar que el usuario de la aplicación sea elegible para votar en las elecciones. Uno de los miembros más antiguos del equipo nos ha señalado que parte del código que necesitamos ya está implementado en un módulo llamado @area/elections y que podemos importarlo de la siguiente manera:

import { isEligibleToVote } from "@area/elections";

La importación es un excelente punto de partida y nos sentimos agradecidos por la ayuda brindada por nuestro compañero de trabajo. Es hora de hacer algo de trabajo. Sin embargo, tenemos un problema. No sabemos cómo usar isEligibleToVote. Si tratamos de adivinar el tipo de isEligibleToVote por su nombre, podríamos suponer que lo más possible es que sea una función, pero no sabemos qué argumentos se le deben proporcionar:

isEligibleToVote(????);

No tenemos miedo de leer el código de otra persona. ¿Abrimos el código fuente del código fuente del @area/elections módulo y nos encontramos con lo siguiente:

const both = (f, g) => arg => f(arg) || g(arg);
const each = (f, g) => arg => f(arg) && g(arg);
const OUR_COUNTRY = "Eire";
const wasBornInCountry = particular person => particular person.birthCountry === OUR_COUNTRY;
const wasNaturalized = particular person => Boolean(particular person.naturalizationDate);
const isOver18 = particular person => particular person.age >= 18;
const isCitizen = both(wasBornInCountry, wasNaturalized);
export const isEligibleToVote = each(isOver18, isCitizen);

El fragmento de código anterior utiliza un estilo de programación funcional. Él isEligibleToVote realiza una serie de comprobaciones:

  • La persona debe ser mayor de 10
  • La persona debe ser ciudadano
  • Para ser ciudadano, la persona debe haber nacido en el país o naturalizado

Necesitamos comenzar a hacer ingeniería inversa en nuestro cerebro para poder decodificar el código anterior. estaba casi seguro de que isEligibleToVote es una función, pero ahora tengo algunas dudas porque no veo el operate funciones de palabra clave o flecha (=>) en su declaración:

const isEligibleToVote = each(isOver18, isCitizen);

PARA poder saber qué es necesitamos examinar cuál es el each función haciendo. Puedo ver que ambos toman dos argumentos f y g y puedo ver que funcionan porque se invocan f(arg) y g(arg). Él each función devuelve una función arg => f(arg) && g(arg) que toma un argumento llamado args y su forma es totalmente desconocida para nosotros en este punto:

const each = (f, g) => arg => f(arg) && g(arg);

Ahora podemos volver a la isEligibleToVote función e intentar examinar de nuevo para ver si podemos encontrar algo nuevo. ahora sabemos que isEligibleToVote es la función devuelta por el each función arg => f(arg) && g(arg) y también sabemos que f es isOver18 y g es isCitizen asi que isEligibleToVote está haciendo algo comparable a lo siguiente:

const isEligibleToVote = arg => isOver18(arg) && isCitizen(arg);

Todavía tenemos que averiguar cuál es el argumento. arg. Podemos examinar el isOver18 y isCitizen funciones para encontrar algunos detalles.

const isOver18 = particular person => particular person.age >= 18;

Este dato es instrumental. Ahora sabemos que isOver18 espera un argumento llamado particular person y que es un objeto con una propiedad llamada age también podemos adivinar por la comparación particular person.age >= 18 eso age es un número

Echemos un vistazo a la isCitizen funcionar también:

const isCitizen = both(wasBornInCountry, wasNaturalized);

No tenemos suerte aquí y necesitamos examinar el both, wasBornInCountry y wasNaturalized funciones:

const both = (f, g) => arg => f(arg) || g(arg);
const OUR_COUNTRY = "Eire";
const wasBornInCountry = particular person => particular person.birthCountry === OUR_COUNTRY;
const wasNaturalized = particular person => Boolean(particular person.naturalizationDate);

Ambos wasBornInCountry y wasNaturalized esperar un argumento llamado particular person y ahora hemos descubierto nuevas propiedades:

  • Él birthCountry la propiedad parece ser una cadena
  • Él naturalizationDate la propiedad parece ser fecha o nula

Él both la función pasa un argumento a ambos wasBornInCountry y wasNaturalized Lo que significa que arg debe ser una persona. Tomó mucho esfuerzo cognitivo y nos sentimos cansados, pero ahora sabemos que podemos usar el isElegibleToVote La función se puede utilizar de la siguiente manera:

isEligibleToVote({
    age: 27,
    birthCountry: "Eire",
    naturalizationDate: null
});

Podríamos superar algunos de estos problemas utilizando documentación como JSDoc. Sin embargo, eso significa más trabajo y la documentación puede quedar obsoleta rápidamente.

TypeScript puede ayudar a validar que nuestras anotaciones JSDoc estén actualizadas con nuestra base de código. Sin embargo, si vamos a hacer eso, ¿por qué no adoptar TypeScript en primer lugar?

La vida con tipos en una base de código de programación funcional #

Ahora que sabemos lo difícil que es trabajar en una base de código de programación funcional sin tipos, vamos a ver cómo se siente trabajar en una base de código de programación funcional con tipos estáticos. Vamos a volver al mismo punto de partida, nos hemos incorporado a una empresa, y uno de nuestros compañeros nos ha indicado el @area/elections módulo. Sin embargo, esta vez nos encontramos en un universo paralelo y el código base está tecleado estáticamente.

import { isEligibleToVote } from "@area/elections";

no sabemos si isEligibleToVote es función. Sin embargo, esta vez podemos hacer mucho más que adivinar. Podemos usar nuestro IDE para desplazarnos sobre el isEligibleToVote variable para confirmar que es una función:

Entonces podemos intentar invocar el isEligibleToVote y nuestro IDE nos avisará que necesitamos pasar un objeto de tipo Individual como argumento:

Si intentamos pasar un objeto literal, nuestro IDE mostrará todas las propiedades y del Individual tipo junto con sus tipos:

¡Eso es! ¡No se requiere pensar ni documentarse! Todo gracias al sistema de tipos TypeScript.

El siguiente fragmento de código contiene la versión con seguridad de tipos del @area/elections módulo:

interface Individual  null;
    age: quantity;


const both = <T1>(
   f: (a: T1) => boolean,
   g: (a: T1) => boolean
) => (arg: T1) => f(arg) || g(arg);

const each = <T1>(
   f: (a: T1) => boolean,
   g: (a: T1) => boolean
) => (arg: T1) => f(arg) && g(arg);

const OUR_COUNTRY = "Eire";
const wasBornInCountry = (particular person: Individual) => particular person.birthCountry === OUR_COUNTRY;
const wasNaturalized = (particular person: Individual) => Boolean(particular person.naturalizationDate);
const isOver18 = (particular person: Individual) => particular person.age >= 18;
const isCitizen = both(wasBornInCountry, wasNaturalized);
export const isEligibleToVote = each(isOver18, isCitizen);

Agregar anotaciones de tipo puede requerir un poco de tipo adicional, pero los beneficios sin duda valdrán la pena. Nuestro código será menos propenso a errores, estará autodocumentado y los miembros de nuestro equipo serán mucho más productivos porque pasarán menos tiempo tratando de comprender el código preexistente.

El principio common de UX no me hagas pensar también puede traer grandes mejoras a nuestro código. Recuerda que al remaining del día pasamos mucho más tiempo leyendo que escribiendo código.

Acerca de los tipos en lenguajes de programación funcionales #

Los lenguajes de programación funcionales no tienen que escribirse estáticamente. Sin embargo, los lenguajes de programación funcionales tienden a escribirse estáticamente. Según Wikipedia, esta tendencia se ha estado aclarando desde la década de 1970:

Desde el desarrollo de la inferencia de tipo Hindley-Milner en la década de 1970, los lenguajes de programación funcional han tendido a utilizar el cálculo lambda tipificado, rechazando todos los programas inválidos en el momento de la compilación y arriesgándose a errores falsos positivos, a diferencia del cálculo lambda no tipificado, que acepta todos los programas válidos. en tiempo de compilación y corre el riesgo de errores falsos negativos, utilizados en Lisp y sus variantes (como Scheme), aunque rechazan todos los programas inválidos en tiempo de ejecución, cuando la información es suficiente para no rechazar programas válidos. El uso de tipos de datos algebraicos hace conveniente la manipulación de estructuras de datos complejas; la presencia de una fuerte verificación de tipos en tiempo de compilación hace que los programas sean más confiables en ausencia de otras técnicas de confiabilidad como el desarrollo basado en pruebas, mientras que la inferencia de tipos libera al programador de la necesidad de declarar tipos manualmente al compilador en la mayoría de los casos.

Consideremos una implementación orientada a objetos del isEligibleToVote función sin tipos:

const OUR_COUNTRY = "Eire";

export class Individual {
    constructor(birthCountry, age, naturalizationDate) {
        this._birthCountry = birthCountry;
        this._age = age;
        this._naturalizationDate = naturalizationDate;
    }
    _wasBornInCountry() {
        return this._birthCountry === OUR_COUNTRY;
    }
    _wasNaturalized() {
        return Boolean(this._naturalizationDate);
    }
    _isOver18() {
        return this._age >= 18;
    }
    _isCitizen()  this._wasNaturalized();
    
    isEligibleToVote() {
        return this._isOver18() && this._isCitizen();
    }
}

Averiguar cómo se debe invocar el código anterior no es una tarea trivial:

import { Individual } from "@area/elections";

new Individual("Eire", 27, null).isEligibleToVote();

Una vez más, sin tipos, nos vemos obligados a echar un vistazo a los detalles de implementación.

constructor(birthCountry, age, naturalizationDate) {
    this._birthCountry = birthCountry;
    this._age = age;
    this._naturalizationDate = naturalizationDate;
}

Cuando usamos tipos estáticos, las cosas se vuelven más fáciles:

const OUR_COUNTRY = "Eire";

class Individual {

    personal readonly _birthCountry: string;
    personal readonly _naturalizationDate: Date | null;
    personal readonly _age: quantity;

    public constructor(
        birthCountry: string,
        age: quantity,
        naturalizationDate: Date | null
    ) {
        this._birthCountry = birthCountry;
        this._age = age;
        this._naturalizationDate = naturalizationDate;
    }

    personal _wasBornInCountry() {
        return this._birthCountry === OUR_COUNTRY;
    }

    personal _wasNaturalized() {
        return Boolean(this._naturalizationDate);
    }

    personal _isOver18() {
        return this._age >= 18;
    }

    personal _isCitizen()  this._wasNaturalized();
    

    public isEligibleToVote() {
        return this._isOver18() && this._isCitizen();
    }

}

El constructor nos cube cuántos argumentos se necesitan y los tipos esperados de cada uno de los argumentos:

public constructor(
    birthCountry: string,
    age: quantity,
    naturalizationDate: Date | null
) {
    this._birthCountry = birthCountry;
    this._age = age;
    this._naturalizationDate = naturalizationDate;
}

Personalmente, creo que la programación funcional suele ser más difícil de aplicar ingeniería inversa que la programación orientada a objetos. Tal vez esto se deba a mi formación orientada a objetos. Sin embargo, sea cual sea la razón, estoy seguro de una cosa: los tipos realmente me facilitan la vida, y sus beneficios son aún más notables cuando estoy trabajando en una base de código de programación funcional.

Resumen #

Los tipos estáticos son una valiosa fuente de información. Dado que pasamos mucho más tiempo leyendo código que escribiendo código, debemos optimizar nuestro flujo de trabajo para que podamos ser más eficientes leyendo código en lugar de escribir código. Los tipos pueden ayudarnos a eliminar una gran cantidad de esfuerzo cognitivo para que podamos centrarnos en el problema comercial que estamos tratando de resolver.

Si bien todo esto es cierto en las bases de código de programación orientada a objetos, los beneficios son aún más notables en las bases de código de programación funcional, y es exactamente por eso que me gusta argumentar que TypeScript es una mejor opción que JavaScript cuando se trata de programación funcional. ¿Qué piensas?

Si disfrutó de esta publicación y está interesado en la programación funcional o TypeScript, consulte mi próximo libro Programación funcional práctica con TypeScript

20

Prestigio

20

Prestigio

Related Articles

Comments

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Same Category

Agregación de datos financieros para pymes con Jason Dryhurst-Smith

Este episodio es apoyado por Expertos de AWS....

Nueva tecnología de mejora de la privacidad para todos

Publicado por Miguel Guevara, Product Supervisor, Oficina de...
spot_img

Stay in touch!

Follow our Instagram