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