diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8368362..1a45a06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: - name: Install Calibre run: | - sudo apt install -y libopengl0 libegl1 + sudo apt install -y libopengl0 libegl1 libxcb-cursor0 wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin mkdir -p ~/.local/bin ln -s /opt/calibre/calibre ~/.local/bin/calibre diff --git a/.gitignore b/.gitignore index 90efdc8..47f07f3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ npm-debug.log .DS_Store *.pdf *.epub +_book diff --git a/README.md b/README.md index 9c4ef33..080728c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Sobre este libro -Este es un libro sobre el paradigma funcional en general. Utilizaremos el lenguaje de programación funcional más popular del mundo: JavaScript. Algunos pueden pensar que es una mala elección, ya que va en contra de la cultura que, por el momento, es predominantemente imperativa. Sin embargo, creo que esta es la mejor forma de aprender programación funcional por diversas razones: +Este es un libro sobre el paradigma funcional en general. Utilizaremos el lenguaje de programación funcional más popular del mundo: JavaScript. Hay quien vaya a pensar que es una mala elección, ya que va en contra de la cultura que, por el momento, es predominantemente imperativa. Sin embargo, creo que esta es la mejor forma de aprender programación funcional por diversas razones: * **Seguramente lo utilizes cada día en el trabajo.** @@ -18,17 +18,19 @@ Este es un libro sobre el paradigma funcional en general. Utilizaremos el lengua * **El lenguaje está completamente capacitado para escribir código funcional de primera categoría.** - Tenemos todas las características necesarias para imitar a un lenguaje como Scala o Haskell con la ayuda de una o dos pequeñas librerías. La programación orientada a objetos domina actualmente la industria, pero es claramente torpe en JavaScript. Es similar a acampar en una autopista o bailar claqué con botas de agua. Tenemos que usar `bind` por todas partes para que `this` no cambie sin nosotros saberlo, tenemos varias soluciones para el extraño comportamiento cuando olvidas usar `new`, los miembros privados solo están disponibles mediante closures. Para muchos de nosotros, la programación funcional parece más natural de todos modos. + Tenemos todas las características necesarias para imitar a un lenguaje como Scala o Haskell con la ayuda de una o dos pequeñas librerías. La programación orientada a objetos domina actualmente la industria, pero es claramente torpe en JavaScript. Es similar a acampar en una autopista o bailar claqué con botas de agua. Tenemos que usar `bind` por todas partes para que `this` no cambie sin que nos demos cuenta, tenemos varias alternativas al peculiar comportamiento cuando olvidamos utilizar `new`, los miembros privados solo están disponibles mediante clausuras [*closures*]. En fin, para muchas personas la programación funcional parece más natural. -Dicho esto, los lenguajes funcionales tipados serán, sin ninguna duda, el mejor lugar para programar con el estilo que se presenta en este libro. JavaScript será nuestro medio para aprender un paradigma, dónde lo apliques depende de tí. Afortunadamente, las interfaces son matemáticas y, como tal, ubicuas. Te sentirás como en casa con Swiftz, Scalaz, Haskell, PureScript, y otros entornos inclinados hacia las matemáticas. +Dicho esto, los lenguajes funcionales tipados serán, sin ninguna duda, el mejor lugar para programar con el estilo que se presenta en este libro. JavaScript será nuestro medio para aprender un paradigma, dónde lo apliques depende de tí. Afortunadamente, las interfaces son matemáticas y, como tal, ubicuas. Te sentirás como en casa con Swiftz, Scalaz, Haskell, PureScript, y otros entornos con inclinación por las matemáticas. ## Sobre la traducción Se han añadido notas de traducción donde se ha visto necesario. Para no interferir con el ritmo de la lectura se ha optado por incluir la nota entre corchetes y en cursiva seguidamente de aquello que se esté anotando. Por ejemplo, "solo necesitas saber cómo encontrar y matar algunos bugs [*bichos*]". En caso de anotar un título la anotación se incluirá al comienzo del párrafo que le siga. +También se han modificado algunas frases para mantener un género neutro. Por ejemplo en vez de traducir "Some will argue that" a "Algunos argumentarán que", se ha traducido a "Hay quien argumentará que". + ## Léelo Online -Para una mejor experiencia de lectura, [léelo online a través de Gitbook](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/). +Para una mejor experiencia en la lectura, [léelo online a través de Gitbook](https://mostly-adequate.gitbooks.io/mostly-adequate-guide/). - Barra lateral de acceso rápido - Ejercicios en el propio navegador @@ -37,7 +39,7 @@ Para una mejor experiencia de lectura, [léelo online a través de Gitbook](http ## Juega Con el Código -Para que el entrenamiento sea más efectivo y que no te aburras demasiado cuando te esté contando otra historia, asegúrate de jugar con los conceptos introducidos en este libro. Algunos pueden ser difíciles de entender a la primera y se comprenden mejor cuándo te ensucias las manos. +Para que el entrenamiento sea efectivo y no te aburras demasiado mientras te cuento otra história, asegúrate de jugar con los conceptos introducidos en este libro. Algunos pueden ser difíciles de entender a la primera y se comprenden mejor cuándo te ensucias las manos. Todas las funciones y estructuras de datos algebraicas presentadas en el libro están reunidas en los apéndices. El correspondiente código también está disponible como un módulo de npm: ```bash @@ -91,7 +93,7 @@ Ver [FAQ-es.md](FAQ-es.md) ## Planes para el futuro * **Parte 1** (capítulos 1-7) es una guía básica. La actualizaré a medida que encuentre errores, ya que esto es un borrador inicial. ¡Siéntete libre de ayudar! -* **Parte 2** (capítulos 8-13) aborda de forma transversal clases de tipos, como funtores y mónadas. Espero poder meterme con transformers y con una aplicación pura. +* **Parte 2** (capítulos 8-13) aborda clases de tipos como funtores y mónadas llegando hasta traversable. Espero poder alcanzar transformadores y una aplicación pura. * **Parte 3** (capítulos 14+) cruzará la delgada línea entre la programación práctica y la absurdidad académica. Veremos comónadas, f-algebras, mónadas libres, yoneda, y otras construcciones categóricas. diff --git a/SUMMARY-es.md b/SUMMARY-es.md index 86ca2d6..fbf7662 100644 --- a/SUMMARY-es.md +++ b/SUMMARY-es.md @@ -58,9 +58,9 @@ * [En Resumen](ch08-es.md#en-resumen) * [Ejercicios](ch08-es.md#ejercicios) * [Capítulo 09: Cebollas Monádicas](ch09-es.md) - * [Factoría de Funtores Punzantes](ch09-es.md#factoría-de-funtores-punzantes) + * [Factoría de Funtores Puntiagudos](ch09-es.md#factoría-de-funtores-puntiagudos) * [Mezclando Metáforas](ch09-es.md#mezclando-metáforas) - * [Mi Cadena Golpea Mi Pecho](ch09-es.md#mi-cadena-golpea-mi-pecho) + * [Mi Cadena Me Golpea El Pecho](ch09-es.md#mi-cadena-me-golpea-el-pecho) * [Borrachera de Poder](ch09-es.md#borrachera-de-poder) * [Teoría](ch09-es.md#teoría) * [En Resumen](ch09-es.md#en-resumen) @@ -99,7 +99,7 @@ * [Abstrayendo La Suma](ch13-es.md#abstrayendo-la-suma) * [Todos Mis Funtores Favoritos Son Semigrupos](ch13-es.md#todos-mis-funtores-favoritos-son-semigrupos) * [Monoides A Cambio De Nada](ch13-es.md#monoides-a-cambio-de-nada) - * [Doblando La Casa](ch13-es.md#doblando-la-casa) + * [Plegando La Casa](ch13-es.md#plegando-la-casa) * [No Un Monoide Exactamente](ch13-es.md#no-un-monoide-exactamente) * [Gran Teoría Unificadora](ch13-es.md#gran-teoría-unificadora) * [¿Teoría De Grupos O Teoría De Categorías?](ch13-es.md#teoría-de-grupos-o-teoría-de-categorías) diff --git a/book.json b/book.json index 430040c..56312d1 100644 --- a/book.json +++ b/book.json @@ -5,7 +5,7 @@ "cover": "images/cover.png", "plugins": [ "exercises@git+https://github.com/MostlyAdequate/plugin-exercises.git", - "include-codeblock@3.2.2" + "include-codeblock@3.2.3" ], "structure": { "summary": "SUMMARY-es.md" diff --git a/ch01-es.md b/ch01-es.md index d60b6fa..cd2fe27 100644 --- a/ch01-es.md +++ b/ch01-es.md @@ -2,15 +2,15 @@ ## Presentaciones -¡Hola! Soy el Profesor Franklin Frisby, encantado de conocerte. Vamos a pasar algo de tiempo juntos, pues se supone que voy a enseñarte un poco de programación funcional. Pero basta de hablar sobre mí, ¿qué hay de ti? Espero que estés al menos un poco familiarizado con el lenguaje JavaScript, que tengas un poco de experiencia en programación orientada a objetos, y que te apetezca convertirte en un programador a seguir. No necesitas tener un doctorado en entomología, solo necesitas saber cómo encontrar y matar algunos bugs [*bichos*]. +¡Hola! Soy el Profesor Franklin Frisby, encantado de conocerte. Vamos a pasar algo de tiempo juntos, pues se supone que voy a enseñarte algo de programación funcional. Pero basta de hablar sobre mí, ¿qué hay de ti? Espero que el lenguaje JavaScript te sea por lo menos familiar, que tengas algo de experiencia en programación orientada a objetos, y que te apetezca convertirte en un programador de bandera. No necesitas tener un doctorado en entomología, solo necesitas saber cómo encontrar y matar algunos bugs [*bichos*]. No asumo que tengas ningún conocimiento previo sobre programación funcional porque ya sabemos lo que sucede cuando uno presupone, pero espero que hayas encontrado problemas al trabajar con estados mutables, con efectos secundarios no restringidos, y con diseño sin principios. Ahora que ya nos hemos presentado, sigamos adelante. El propósito de este capítulo es darte una idea de lo que buscamos cuando escribimos programas funcionales. Para poder entender los próximos capítulos, hemos de tener una idea sobre qué hace que un programa sea *funcional*. De lo contrario, acabaremos garabateando sin rumbo, evitando objetos a toda costa; un esfuerzo sin sentido. Necesitamos una diana a la que lanzar nuestro código, una brújula celestial para cuando las aguas se agiten. -Hay ciertos principios de programación, varios acrónimos, que nos guiarán a través de los túneles oscuros de cualquier aplicación: DRY (don't repeat yourself [*no te repitas*]), YAGNI (ya ain't gonna need it [*no lo vas a necesitar*]), alta cohesión bajo acoplamiento, principio de mínima sorpresa, única responsabilidad, etc. +Hay ciertos principios de programación, varios acrónimos, que nos guiarán a través de los túneles oscuros de cualquier aplicación: DRY (don't repeat yourself [*no te repitas*]), YAGNI (ya ain't gonna need it [*no lo vas a necesitar*]), alta cohesión bajo acoplamiento, principio de mínima sorpresa, responsabilidad única, etc. -No voy a alargarme enumerando cada una de las guías que he escuchado a lo largo de los años... La cuestión es que siguen vigentes en un entorno funcional, aunque son tangenciales a nuestro objetivo final. +No voy a alargarme enumerando cada una de las guías que he escuchado a lo largo de los años... La cuestión es que siguen vigentes en un entorno funcional, aunque de forma meramente tangencial a nuestro objetivo final. Lo que me gustaría que entendieses por ahora, antes de seguir adelante, es cuál será nuestra intención cuando nos aferremos al teclado; nuestro Xanadú funcional. @@ -47,7 +47,7 @@ const result = flockA // 32 ``` -¿Quién en la faz de la tierra, sería capaz de crear esta espantosa abominación? Es irrazonablemente difícil mantener el rastro del estado mutable interno. Y, por si fuera poco, ¡la respuesta es incorrecta! Debería ser `16`, pero `flockA` ha sido alterado permanentemente durante el proceso. Pobre `flockA`. ¡Esto es anarquía en la informática! ¡Esto es aritmética de animales salvajes! +¿Quién en la faz de la tierra, sería capaz de crear tan espantosa abominación? Es irrazonablemente difícil seguir el rastro del estado interno mientras muta. Y, por si esto fuera poco, ¡la respuesta es incorrecta! Debería ser `16`, pero `flockA` ha sido alterado permanentemente durante el proceso. Pobre `flockA`. ¡Esto es anarquía en la informática! ¡Esto es aritmética de animales salvajes! Si no entiendes este programa, no pasa nada, yo tampoco lo entiendo. La cuestión es que el estado y los valores mutables son difíciles de seguir, incluso en un ejemplo tan pequeño. @@ -96,7 +96,7 @@ add(x, 0) === x; multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z)); ``` -Ah, sí, esas viejas y fieles propiedades matemáticas serán de ayuda. No te preocupes si no las sabes de memoria. Para muchos de nosotros ha pasado mucho tiempo desde que las estudiamos. Vamos a ver si podemos utilizar estas propiedades para simplificar nuestra pequeña aplicación de gaviotas. +Ah, sí, esas viejas y fieles propiedades matemáticas serán de ayuda. No te preocupes si no las sabes de memoria. Puede que haya pasado mucho tiempo desde que las estudiamos. Vamos a ver si podemos utilizar estas propiedades para simplificar nuestra pequeña aplicación de gaviotas. ```js // Línea original @@ -112,14 +112,14 @@ multiply(flockB, add(flockA, flockA)); ¡Brillante! No hemos tenido que escribir ni una pizca de código aparte de las llamadas a las funciones. Hemos incluído las implementaciones de `add` y `multiply` por completitud, pero en realidad no hacía falta escribirlas, puesto que seguro que ya existen en alguna librería. -Seguramente estarás pensando "qué pícaro, al poner este ejemplo". O "en la realidad los programas no son tan simples y no se puede razonar sobre ellos de esta manera". He seleccionado este ejemplo porque la mayoría de nosotros ya sabemos sumar y multiplicar, así que es fácil ver cómo las matemáticas pueden sernos útiles. +Seguramente estarás pensando "qué pícaro, al poner este ejemplo". O "en la realidad los programas no son tan simples y no se puede razonar sobre ellos de esta manera". He seleccionado este ejemplo porque la mayoría ya sabemos sumar y multiplicar, y así es fácil ver cómo las matemáticas pueden sernos útiles. -No te desesperes, a lo largo de este libro hablaremos un poco sobre teoría de categorías, teoría de conjuntos, y cálculo lambda para escribir ejemplos de la vida real que consigan la misma elegante simplicidad y que resulten como nuestro ejemplo de la bandada de gaviotas. No necesitas ser un matemático, será como utilizar otro framework u otra API. +No te desesperes, a lo largo de este libro hablaremos un poco sobre teoría de categorías, teoría de conjuntos, y cálculo lambda para escribir ejemplos de la vida real que consigan la misma elegante simplicidad y que resulten como nuestro ejemplo de la bandada de gaviotas. Tampoco necesitas tener un título en matemáticas, será sencillo y natural, como utilizar otro framework u otra API. -Puede resultar sorprendente oír que se pueden escribir aplicaciones completas y reales utilizando programación funcional tal y como hemos mostrado en el ejemplo anterior. Programas con sólidas propiedades. Programas cortos, pero fáciles de razonar. Programas que no reinventan la rueda una y otra vez. La falta de leyes es buena si eres un criminal, pero en este libro, vamos a reconocer y a obedecer a las leyes de las matemáticas. +Puede resultar sorprendente oír que se pueden escribir aplicaciones completas y reales utilizando programación funcional tal y como hemos mostrado en el ejemplo anterior. Programas con sólidas propiedades. Programas cortos sobre los que razonar fácilmente. Programas que no reinventan la rueda una y otra vez. La falta de leyes es buena si vas a cometer un crimen, pero en este libro, vamos a reconocer y a obedecer a las leyes de las matemáticas. -Querremos utilizar una teoría en la que todas las piezas tiendan a encajar limpiamente. Querremos representar nuestro problema específico en términos de pequeñas piezas genéricas y combinables, para luego explotar sus propiedades en nuestro propio beneficio. Será necesaria un poco más de disciplina que en el enfoque del "todo vale" de la programación imperativa (más adelante definiremos más precisamente lo que es la programación imperativa, pero por ahora considérala cualquier cosa que no sea programación funcional). La recompensa de trabajar dentro de un marco de trabajo basado en principios matemáticos realmente te asombrará. +Querremos utilizar una teoría en la que todas las piezas tiendan a encajar limpiamente. Querremos representar nuestro problema específico en términos de pequeñas piezas genéricas y combinables, para luego explotar sus propiedades en nuestro propio beneficio. Será necesaria un poco más de disciplina que en el enfoque del "todo vale" de la programación imperativa (más adelante definiremos más precisamente qué es la programación imperativa, pero por ahora considérala cualquier cosa que no sea programación funcional). La recompensa de trabajar dentro de un marco matemático basado en principios verdaderamente te asombrará. -Hemos visto un destello de nuestra estrella del norte funcional, pero hay unos cuantos conceptos que necesitamos entender antes de poder empezar realmente nuestro viaje. +Hemos visto un destello de nuestra estrella del norte funcional, pero hay unos cuantos conceptos que necesitamos entender antes de realmente poder emprender nuestro viaje. [Capítulo 2: Funciones de Primera Clase](ch02-es.md) diff --git a/ch02-es.md b/ch02-es.md index 4f8c193..4f602ef 100644 --- a/ch02-es.md +++ b/ch02-es.md @@ -10,7 +10,7 @@ const hi = name => `Hi ${name}`; const greeting = name => hi(name); ``` -Aquí, en `greeting`, la función que envuelve `hi` es completamente redundante. ¿Por qué? Porque las funciones son *llamables* en JavaScript. Cuando `hi` tiene los `()` al final, se ejecutará y devolverá un valor. Cuando no los tiene, simplemente devolverá la función almacenada en la variable. Solo para estar seguro, echa un vistazo tú mismo. +Aquí, en `greeting`, la función que envuelve a `hi` es completamente redundante. ¿Por qué? Porque las funciones son *llamables* en JavaScript. Cuando `hi` tiene los `()` al final, se ejecutará y devolverá un valor. Cuando no los tiene, simplemente devolverá la función almacenada en la variable. Solo para asegurarte, echa un vistazo tú mismo. ```js hi; // name => `Hi ${name}` @@ -26,7 +26,7 @@ greeting("times"); // "Hi times" En otras palabras, `hi` ya es una función que espera un argumento, ¿por qué colocar otra función alrededor de ella que simplemente llame a `hi` con el mismo condenado argumento? No tiene ningún maldito sentido. Es como ponerte tu parka más pesada al final de un julio mortal solo para subir el aire acondicionado y pedir un helado. -Es demasiado detallado y, también, una mala práctica rodear una función con otra función simplemente para retrasar la evaluación. (Veremos por qué en un momento, pero tiene que ver con el mantenimiento.) +Rodear una función con otra función simplemente para retrasar la evaluación es demasiado detallado y también una mala práctica. (Veremos por qué en un momento, pero tiene que ver con el mantenimiento.) Es fundamental comprender bien esto antes de continuar, así que vamos a examinar algunos otros divertidos ejemplos extraídos de paquetes de npm. @@ -66,7 +66,7 @@ const BlogController = { }; ``` -Este ridículo controlador es 99% aire. Podríamos ya sea reescribirlo como: +Este ridículo controlador es 99% aire. Podríamos reescribirlo como: ```js const BlogController = { @@ -104,7 +104,7 @@ Si la hubiéramos escrito como una función de primera clase, mucho menos necesi httpGet('/post/2', renderPost); ``` -Además de la eliminación de funciones innecesarias, debemos nombrar y referenciar argumentos. Los nombres son un poco problemáticos. Tenemos potenciales errores de nombrado, especialmente cuando la base de código crece y los requerimientos cambian. +Si no eliminamos las funciones innecesarias deberemos nombrar a los argumentos y hacer referencia a ellos. Los nombres son algo problemáticos, ya sabes. Existen potenciales errores de nombrado, especialmente cuando la base de código envejece y los requerimientos cambian. Tener múltiples nombres para el mismo concepto suele ser una fuente de confusión en los proyectos. También existe el problema del código genérico. Por ejemplo, estas dos funciones hacen exactamente lo mismo, pero una es infinitamente más general y reusable. @@ -133,7 +133,7 @@ fs.readFile('freaky_friday.txt', Db.save.bind(Db)); Después de haber sido enlazada a sí misma, `Db` es libre de acceder a su prototípico código basura. Yo evito usar `this` de la misma manera que evito usar un pañal sucio. Realmente no hay ninguna necesidad cuando se escribe código funcional. Sin embargo, al interactuar con otras bibliotecas, tendrás que aceptar el loco mundo que nos rodea. -Algunos argumentarán que `this` es necesario para optimizar la velocidad. Si eres del tipo micro-optimizador, por favor cierra este libro. Si no puedes recuperar tu dinero, quizás puedas intercambiarlo por algo más complejo. +Hay quien argumentará que `this` es necesario para optimizar la velocidad. Si te va la micro-optimización, por favor cierra este libro. Si no puedes recuperar tu dinero, quizás puedas intercambiarlo por algo más complejo. Y con esto estamos listos para seguir adelante. diff --git a/ch03-es.md b/ch03-es.md index 3e98501..3eabf86 100644 --- a/ch03-es.md +++ b/ch03-es.md @@ -4,7 +4,7 @@ Una cosa que necesitamos para comenzar correctamente es la idea de una función pura. ->Una función pura es una función que, dada la misma entrada, siempre devolverá la misma salida y no contiene ningún efecto secundario observable. +>Una función pura es una función que, dada la misma entrada, siempre devolverá la misma salida y que no contiene ningún efecto secundario observable. Toma por ejemplo `slice` y `splice`. Son dos funciones que hacen exactamente lo mismo, eso sí, de una forma muy diferente, pero lo mismo al fin y al cabo. Decimos que `slice` es *pura* porque siempre devuelve la misma salida para cada entrada, garantizado. `splice`, sin embargo, se comerá su array y lo escupirá cambiado para siempre, lo cual es un efecto observable. @@ -27,7 +27,7 @@ xs.splice(0,3); // [4,5] xs.splice(0,3); // [] ``` -En programación funcional, no nos gustan las funciones poco manejables como `splice`, que muta datos. Esto no es aceptable, ya que nos esforzamos por tener funciones en las que podamos confiar, que devuelvan siempre la misma salida, no funciones que dejan un desastre a su paso como `splice`. +En programación funcional, no nos gustan las funciones poco manejables como `splice`, que muta los datos. Esto no es aceptable, ya que nos esforzamos por tener funciones en las que podamos confiar, que devuelvan siempre la misma salida, no funciones que dejan un desastre a su paso como `splice`. Veamos otro ejemplo. @@ -45,7 +45,7 @@ const checkAge = (age) => { En la parte impura, `checkAge` depende de la variable mutable `minimum` para determinar el resultado. En otras palabras, depende del estado del sistema, lo que es decepcionante porque incrementa la [carga cognitiva](https://es.wikipedia.org/wiki/Teoría_de_la_carga_cognitiva) al introducir un entorno externo. -Puede que no parezca mucho en este ejemplo, pero esta dependencia sobre el estado es una de las mayores contribuciones a la complejidad de los sistemas(http://www.curtclifton.net/storage/papers/MoseleyMarks06a.pdf). Esta `checkAge` puede devolver un resultado diferente dependiendo de factores externos a la entrada, lo que no solo la descalifica como pura, sino que también pone a prueba a nuestra mente cada vez que razonamos sobre el software. +Puede que no parezca mucho en este ejemplo, pero esta dependencia sobre el estado es una de las mayores contribuciones a la complejidad de los sistemas(http://www.curtclifton.net/storage/papers/MoseleyMarks06a.pdf). Esta `checkAge` puede devolver un resultado diferente dependiendo de factores externos a la entrada, lo que no solo la descalifica como pura, sino que además pone a prueba a nuestra mente cada vez que razonamos sobre el software. Por otro lado, su forma pura, es completamente autosuficiente. También podemos hacer que `minimum` sea inmutable, lo que preserva la pureza, ya que el estado nunca cambia. Para hacer esto, debemos crear un objeto para poder congelarlo. @@ -66,19 +66,19 @@ Los efectos secundarios pueden incluir, pero no limitarse a * cambiar el sistema de ficheros * insertar un registro en una base de datos * hacer una llamada http - * mutaciones + * mutar valores * imprimir en pantalla/registro * obtener entrada del usuario * consultar el DOM * acceder al estado del sistema -Y el listado sigue y sigue. Cualquier interacción con el mundo de afuera de una función es un efecto secundario, hecho que puede llevarte a sospechar de la practicidad de programar sin ellos. La filosofía de la programación funcional postula que los efectos secundarios son la principal causa de las incorrecciones en el comportamiento. +Y el listado sigue y sigue. Cualquier interacción de una función con el mundo exterior es un efecto secundario, hecho que puede llevarte a sospechar de la practicidad de programar sin ellos. La filosofía de la programación funcional postula que los efectos secundarios son la principal causa de las incorrecciones en el comportamiento. -No es que tengamos prohibido usarlos, más bien queremos contenerlos y ejecutarlos de manera controlada. Aprenderemos como hacerlo cuando lleguemos a los funtores y mónadas en capítulos posteriores, pero por ahora, trataremos de mantener estas insidiosas funciones apartadas de las puras. +No es que tengamos prohibido usarlos, más bien queremos contenerlos y ejecutarlos de manera controlada. Aprenderemos como hacerlo cuando lleguemos a los funtores y mónadas en capítulos posteriores, pero por ahora, trataremos de mantener a estas insidiosas funciones apartadas de las puras. Los efectos secundarios descalifican a una función para ser *pura* y tiene sentido: las funciones puras, por definición, deben devolver siempre la misma salida dada la misma entrada, lo que no es garantizable cuando se manejan asuntos externos a nuestra función local. -Veamos con más detalle por qué insistimos en la misma salida por cada entrada. Levantaos el cuello de las camisas, vamos a ver algo de matemáticas de octavo grado [*correspondiente a alumnos de entre 13 y 14 años en el sistema educativo estadounidense*]. +Veamos con más detalle por qué insistimos en lo de la misma salida para cada entrada. Levantaos el cuello de las camisas, vamos a ver algo de matemáticas de octavo grado [*estudiantes de entre 13 y 14 años]. ## Matemáticas de Octavo Grado @@ -87,7 +87,7 @@ De mathisfun.com: > Una función es una relación especial entre valores: > Cada uno de sus valores de entrada devuelve exactamente un valor de salida. -En otras palabras, es solo una relación entre dos valores: la entrada y la salida. Aunque cada entrada tiene exactamente una salida, esa salida no tiene que ser necesariamente única por cada entrada. El siguiente diagrama muestra una función perfectamente válida de `x` a `y`; +En otras palabras, tan solo es una relación entre dos valores: la entrada y la salida. Aunque cada entrada tiene exactamente una salida, esa salida no tiene que ser necesariamente única por cada entrada. El siguiente diagrama muestra una función de `x` a `y` perfectamente válida; conjuntos de funciones(http://www.mathsisfun.com/sets/function.html) @@ -104,7 +104,7 @@ O incluso como un gráfico con `x` como la entrada e `y` como la salida: grafo de funciones -No hay necesidad de detalles de implementación si la entrada dicta la salida. Ya que las funciones son simplemente mapeos de entrada a salida, uno puede simplemente apuntar los valores en objetos y ejecutarlos con `[]` en lugar de `()`. +No hay necesidad de detalles de implementación si la entrada dicta la salida. Ya que las funciones tan solo son mapeos de entrada a salida, podemos simplemente escribir objetos literales y ejecutarlos con `[]` en lugar de `()`. ```js const toLowerCase = { @@ -130,7 +130,7 @@ isPrime[3]; // true Por supuesto, puedes querer calcular en lugar de apuntar valores a mano, pero esto ilustra una forma diferente de pensar sobre las funciones. (Debes estar pensando "¿qué pasa con las funciones con múltiples argumentos?". Ciertamente, esto presenta un pequeño inconveniente cuando se piensa en términos matemáticos. Por ahora, podemos empaquetarlos en un array o simplemente pensar que como entrada pasamos el objeto `arguments`. Cuando aprendamos sobre *currying*, veremos cómo podemos modelar directamente la definición matemática de función.) -Aquí viene la dramática revelación: Las funciones puras *son* funciones matemáticas y ellas son todo sobre lo que trata la programación funcional. Programar con estos pequeños ángeles puede tener grandes beneficios. Veamos algunas de las razones por las que estamos dispuestos a recorrer tan grandes distancias para preservar la pureza. +Aquí viene la dramática revelación: Las funciones puras *son* funciones matemáticas y son todo sobre lo que trata la programación funcional. Programar con estos pequeños ángeles puede tener grandes beneficios. Veamos algunas de las razones por las que estamos dispuestos a recorrer tan grandes distancias para preservar la pureza. ## Los Argumentos Para La Pureza @@ -194,11 +194,11 @@ const signUp = (Db, Email, attrs) => () => { }; ``` -Este ejemplo demuestra que la función pura debe ser honesta acerca de sus dependencias y, como tal, debe decirnos exactamente qué es lo que hace. Solo por su firma, sabemos que usará una `Db`, `Email` y `attrs`, lo que debería ser, cuanto menos, revelador. +Este ejemplo demuestra que la función pura debe ser honesta acerca de sus dependencias y que como tal debe decirnos exactamente qué es lo que hace. Solo por su firma, sabemos que usará una `Db`, `Email` y `attrs`, lo que debería ser, cuanto menos, revelador. Aprenderemos a crear funciones puras como esta sin limitarnos a tan solo aplazar la evaluación, pero debería quedar claro que la forma pura es mucho más informativa que su escurridiza contraparte que trama quién sabe qué. -Algo más a tener en cuenta es que se nos obliga a "inyectar" dependencias, pasándolas como argumentos, lo que hace a nuestra aplicación más flexible, pues hemos parametrizado nuestra base de datos o cliente de email o lo que sea (no te preocupes, veremos una manera de hacer esto menos tedioso de lo que parece). Si decidimos usar una base de datos diferente solo necesitaremos llamar con ella a nuestra función. Si nos encontramos escribiendo una nueva aplicación en la que nos gustaría reutilizar esta confiable función, simplemente tendremos que pasar a esta función la `Db` y el `Email` que tengamos en ese momento. +Algo más a tener en cuenta es que se nos obliga a "inyectar" dependencias, pasándolas como argumentos, lo que hace a nuestra aplicación más flexible, pues hemos parametrizado nuestra base de datos, cliente de email o lo que sea (no te preocupes, veremos una manera de hacer esto menos tedioso de lo que parece). Si decidimos usar una base de datos diferente solo necesitaremos llamar con ella a nuestra función. Si nos encontramos escribiendo una nueva aplicación en la que nos gustaría reutilizar esta confiable función, simplemente tendremos que pasar a esta función la `Db` y el `Email` que tengamos en ese momento. En un entorno JavaScript, portabilidad puede significar serializar y enviar funciones por un socket. Puede significar ejecutar toda nuestra aplicación con Web Workers. La portabilidad es un rasgo poderoso. @@ -210,11 +210,11 @@ Al contrario de los "típicos" métodos y procedimientos de la programación imp Después de lo anterior, nos damos cuenta de que las funciones puras hacen que el testing sea mucho más fácil. No necesitamos mockear una pasarela de pagos "real" o configurar y verificar el estado del mundo después de cada test. Simplemente, pasamos la entrada a la función y verificamos su salida. -De hecho, encontramos que la comunidad funcional está descubriendo nuevas herramientas de pruebas que pueden bombardear nuestra función con entradas generadas y verificar que sus propiedades se mantienen en la salida. Está fuera del alcance de este libro, pero os animo encarecidamente a que busquéis y probéis *Quickcheck*; una herramienta de pruebas que está hecha a medida para un entorno puramente funcional. +De hecho, la comunidad funcional está siendo pionera nuevas herramientas de pruebas que pueden bombardear nuestra función con entradas generadas y verificar que sus propiedades se mantienen en la salida. Está fuera del alcance de este libro, pero os animo encarecidamente a que busquéis y probéis *Quickcheck*; una herramienta de pruebas que está hecha a medida para un entorno puramente funcional. ### Comprensible -Muchos creen que la mayor victoria cuando trabajas con funciones puras es la *transparencia referencial*. Un trozo de código es referencialmente transparente cuando puede ser sustituido por su valor resultante sin cambiar el comportamiento del programa. +Muchas personas creen que la mayor victoria cuando trabajas con funciones puras es la *transparencia referencial*. Un trozo de código es referencialmente transparente cuando puede ser sustituido por su valor resultante sin cambiar el comportamiento del programa. Dado que las funciones puras no tienen efectos secundarios, tan solo pueden influir en el comportamiento de un programa a través de sus valores de salida. Además, puesto que sus valores de salida pueden calcularse de forma fiable con tan solo utilizar sus valores de entrada, las funciones puras siempre mantendrán la transparencia referencial. Veamos un ejemplo. @@ -239,7 +239,7 @@ Primero reemplazamos la función `isSameTeam`. const punch = (a, t) => (a.get('team') === t.get('team') ? t : decrementHP(t)); ``` -Ya que nuestros datos son inmutables, podemos simplemente reemplazar los equipos por sus valores reales +Ya que nuestros datos son inmutables, podemos simplemente reemplazar cada equipo [*team*] por su valor real ```js const punch = (a, t) => ('red' === 'green' ? t : decrementHP(t)); @@ -257,7 +257,7 @@ Y si también reemplazamos `decrementHP`, vemos que, en este caso, `punch` se co const punch = (a, t) => t.set('hp', t.get('hp') - 1); ``` -Esta habilidad para razonar acerca del código es excelente para en general refactorizarlo y entenderlo. De hecho, hemos utilizado esta técnica para refactorizar nuestro programa de bandada de gaviotas. Usamos razonamiento ecuacional para aprovechar las propiedades de adición y multiplicación. De hecho, utilizaremos estas técnicas a lo largo de todo el libro. +Esta habilidad para razonar acerca del código es excelente para, en general, refactorizarlo y entenderlo. De hecho, hemos utilizado esta técnica para refactorizar nuestro programa de bandada de gaviotas. Usamos razonamiento ecuacional para aprovechar las propiedades de adición y multiplicación. De hecho, utilizaremos estas técnicas a lo largo de todo el libro. ### Código Paralelo @@ -267,8 +267,8 @@ Esto podría usarse tanto en un servidor con entorno js e hilos de ejecución co ## En Resumen -Hemos visto qué son las funciones puras y por qué nosotros, como programadores funcionales, creemos que son extraordinarias. De aquí en adelante, nos esforzaremos en escribir todas nuestras funciones de una forma pura. Necesitaremos algunas herramientas adicionales para ayudarnos, pero mientras tanto, trataremos de separar las funciones impuras del resto del código puro. +Hemos visto qué son las funciones puras y por qué en programación funcional creemos que son extraordinarias. De aquí en adelante, nos esforzaremos en escribir todas nuestras funciones de una forma pura. Necesitaremos algunas herramientas adicionales para ayudarnos, pero mientras tanto, trataremos de separar las funciones impuras del resto del código puro. -Escribir programas con funciones puras es algo laborioso sin tener algunas herramientas extra en nuestro cinturón. Hemos de hacer malabares con los datos pasando argumentos por todas partes, tenemos prohibido utilizar estado y sin mencionar lo de los efectos secundarios. ¿Cómo afrontar la escritura de estos programas masoquistas? Obtengamos una nueva herramienta llamada curry. +Resulta un poco laborioso escribir programas con funciones puras al no tener algunas herramientas extra en nuestro cinturón. Hemos de hacer malabares con los datos pasando argumentos por todas partes, tenemos prohibido utilizar estado y sin mencionar lo de los efectos secundarios. ¿Cómo afrontar la escritura de estos programas de masoquista? Obtengamos una nueva herramienta llamada curry. [Capítulo 4: Currying](ch04-es.md) diff --git a/ch04-es.md b/ch04-es.md index 324a2ae..dec1b2b 100644 --- a/ch04-es.md +++ b/ch04-es.md @@ -3,9 +3,9 @@ ## No Puedo Vivir Si Vivir Es Sin Ti [*El título en inglés es 'Can't Live If Livin' Is without You' que recuerda a la canción "Without You" de Badfinger*] -Mi padre una vez me explicó como hay ciertas cosas sin las que se puede vivir hasta que las compras. Un microondas es una de esas cosas. Los smartphones, otra. Los más mayores de nosotros recordarán una vida de plenitud sin internet. Para mí, la `currificación` [*currying en inglés*] está en esta lista. +Mi padre una vez me explicó como hay ciertas cosas sin las que se puede vivir hasta que las compras. Un microondas es una de esas cosas. Los smartphones, otra. Quien sea ya mayor recordará una vida de plenitud sin internet. Para mí, la `currificación` [*currying en inglés*] está en esta lista. -El concepto es sencillo: Puedes llamar a una función con menos argumentos de los que espera. Esta devuelve una función que espera los argumentos restantes. +El concepto es sencillo: Puedes llamar a una función con menos argumentos de los que espera. Esta devolverá una función que esperará los argumentos restantes. Puedes elegir llamarla con todos sus argumentos de una vez o simplemente pasarle cada argumento poco a poco. @@ -18,7 +18,7 @@ increment(2); // 3 addTen(2); // 12 ``` -Aquí hemos hecho una función `add` que acepta un argumento y devuelve una función. A partir de entonces, al llamarla, la función devuelta recuerda el primer argumento mediante la closure. Sin embargo, llamarla con ambos argumentos de una vez es un poco molesto, por lo que podemos utilizar una función de soporte especial llamada `curry` para facilitar la definición y la llamada de funciones como esta. +Aquí hemos hecho una función `add` que acepta un argumento y devuelve una función. A partir de entonces, al llamarla, la función devuelta recuerda el primer argumento mediante el cierre [*closure*]. Sin embargo, llamarla con ambos argumentos de una vez es un poco molesto, por lo que podemos utilizar una función de soporte especial llamada `curry` para facilitar la definición y la llamada de funciones como esta. Vamos a preparar unas pocas funciones currificadas para nuestro disfrute. Desde ahora, nos apoyaremos en nuestra función `curry` definida en el [Apéndice A - Funciones Esenciales de Soporte](./appendix_a-es.md). @@ -53,7 +53,7 @@ censored('Chocolate Rain'); // 'Ch*c*l*t* R**n' Lo que demostramos aquí es la habilidad para precargar una función con un argumento o dos con el fin de recibir una nueva función que recuerda dichos argumentos. -Os aliento a clonar el repositorio de Mostly Adequate (`git clone +Os animo a clonar el repositorio de Mostly Adequate (`git clone https://github.com/MostlyAdequate/mostly-adequate-guide-es.git`), copiar el código anterior y probarlo en la consola REPL. La función curry, igual que cualquier cosa definida en los apéndices, están disponibles en el módulo `support/index.js`. Alternativamente, dale un vistazo a la versión en inglés publicada en `npm`: diff --git a/ch05-es.md b/ch05-es.md index 3f33718..188196d 100644 --- a/ch05-es.md +++ b/ch05-es.md @@ -8,7 +8,7 @@ const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(n ``` ... ¡No te asustes! Este es el nivel-9000-super-Saiyan de _compose_. En aras del razonamiento, ignoremos la implementación variádica y consideremos una forma más simple capaz de componer juntas a dos funciones. Una vez te hayas hecho a la idea, puedes llevar la abstracción más allá y considerar que simplemente funciona para cualquier número de funciones (¡incluso podemos aportar pruebas de ello!) -Aquí tenemos una función _compose_ más amigable para vosotros mis queridos lectores: +Aquí tenemos una función _compose_ más amigable para quiénes me estáis leyendo: ```js const compose2 = (f, g) => x => f(g(x)); @@ -26,7 +26,7 @@ const shout = compose(exclaim, toUpperCase); shout('send in the clowns'); // "SEND IN THE CLOWNS!" ``` -La composición de dos funciones devuelve una nueva función. Esto tiene todo el sentido: componer dos unidades de algún tipo (en este caso función) debería devolver una nueva unidad de ese mismo tipo. No conectas dos legos entre sí y obtienes un "Lincoln Log" [*juego estadounidense de construcción de casitas de madera*]. Existe una teoría aquí, una ley subyacente que descubriremos a su debido tiempo. +La composición de dos funciones devuelve una nueva función. Esto tiene todo el sentido: componer dos unidades de algún tipo (en este caso función) debería devolver una nueva unidad de ese mismo tipo. No conectas dos legos entre sí y obtienes un "Lincoln Log" [*juego de construcción de casitas de madera*]. Existe una teoría aquí, una ley subyacente que descubriremos a su debido tiempo. En nuestra definición de `compose`, la `g` se ejecutará antes que la `f`, creando un flujo de datos de derecha a izquierda. Esto es mucho más legible que tener un montón de funciones anidadas. Sin compose, lo anterior sería: @@ -36,7 +36,7 @@ const shout = x => exclaim(toUpperCase(x)); En vez de adentro hacia afuera, lo ejecutamos de derecha a izquierda, lo cual supongo que es un paso a la izquierda (¡buu!) [*chiste malo que pierde la gracia en la traducción. En inglés, un paso a la derecha también quiere decir un paso en la buena dirección. En este chiste, el paso es hacia la izquierda, o sea, en la mala dirección*]. -Veamos un ejemplo donde la secuencia importa: +Veamos un ejemplo donde el orden en la secuencia importa: ```js const head = x => x[0]; @@ -95,11 +95,11 @@ const loudLastUpper = compose(angry, last); // más variaciones... ``` -No hay repuestas correctas o incorrectas; solo estamos juntando nuestras piezas de lego de la manera que nos plazca. Normalmente, lo mejor es agrupar las cosas de manera que se puedan reutilizar, como `last` y `angry`. Si se conoce a "[Refactoring][refactoring-book]" de Fowler, quizás se pueda reconocer a este proceso como "[extract function][extract-function-refactor]"... excepto por no tener que preocuparse por el estado de ningún objeto. +No hay repuestas correctas o incorrectas; solo estamos juntando nuestras piezas de lego de la manera que nos plazca. Normalmente, lo mejor es agrupar las cosas de manera que se puedan reutilizar, como `last` y `angry`. Si te es familiar "[Refactoring][refactoring-book]" de Fowler, quizás reconozcas a este proceso como "[extract function][extract-function-refactor]"... excepto por no tener que preocuparte por el estado de ningún objeto. ## Pointfree -El estilo pointfree [*que se puede encontrar traducido como programación tácita*] se refiere a nunca tener que hablar sobre tus datos. Perdona. Se refiere a funciones que nunca mencionan los datos sobre los que operan. Las funciones de primera clase, la currificación, y la composición hacen un buen equipo para crear este estilo. +El estilo pointfree [*que se puede encontrar traducido como programación tácita*] se refiere a no hablar nunca sobre tus datos. Perdona. Se refiere a funciones que nunca mencionan los datos sobre los que operan. Las funciones de primera clase, la currificación, y la composición hacen un buen equipo para crear este estilo. > Sugerencia: En el [Apéndice C - Utilidades Pointfree](./appendix_c-es.md) hay definidas versiones pointfree de `replace` y `toLowerCase`. ¡No dudes en echar un vistazo! @@ -190,7 +190,7 @@ const dasherize = compose( dasherize('The world is a vampire'); // 'the-world-is-a-vampire' ``` -La función `trace` nos permite observar los datos en un cierto punto con propósitos de depuración. Lenguajes como Haskell y PureScript tienen funciones similares para facilitar el desarrollo. +A la hora de depurar, la función `trace` nos permite observar los datos en un cierto punto. Lenguajes como Haskell y PureScript tienen funciones similares para facilitar el desarrollo. La composición será nuestra herramienta para construir programas y, afortunadamente, está respaldada por una poderosa teoría que asegura que las cosas funcionarán. Examinemos esta teoría. @@ -243,7 +243,7 @@ const id = x => x; Quizás te preguntes a tí mismo "¿Para qué demonios puede ser esto útil?". En los siguientes capítulos haremos un uso intensivo de esta función, pero por ahora piensa en ella como una función que puede sustituir a nuestro valor; una función que se hace pasar por datos normales y corrientes. -`id` tiene que interactuar bien con compose. Aquí tenemos una propiedad que siempre se cumple para cualquier función unaria f (unaria: función de un solo argumento): +`id` ha de interactuar bien con compose. Aquí tenemos una propiedad que siempre se cumple para cualquier función unaria f (unaria: función de un solo argumento): ```js // identidad @@ -259,9 +259,9 @@ Así que ahí lo tienes, una categoría de tipos y funciones. Si esta es tu prim ## En Resumen -La composición conecta nuestras funciones como si de una especie de tuberías se tratase. Los datos fluirán como es debido a través de nuestra aplicación; las funciones puras son de entrada a salida después de todo, por lo que romper esta cadena descuidaría la salida, volviendo inútil a nuestro software. +La composición conecta nuestras funciones como si de una especie de tuberías se tratase. Los datos fluirán a través de nuestra aplicación como es debido; después de todo las funciones puras van de entrada a salida, por lo que romper esta cadena invalidaría la salida, convirtiendo a nuestro software en inútil. -Mantenemos a la composición como el principio de diseño que está por encima de todos los demás. Esto se debe a que mantiene a nuestra app simple y razonable. La teoría de categorías desempeñará un papel importante en la arquitectura de aplicaciones, modelando los efectos secundarios, y asegurando que está libre de errores. +Consideramos a la composición como el principio de diseño que está por encima de todos los demás. Esto se debe a que mantiene a nuestra app simple y razonable. La teoría de categorías desempeñará un papel importante en la arquitectura de aplicaciones, modelando los efectos secundarios, y asegurando que está libre de errores. Hemos llegado a un punto donde nos será útil ver algo de esto en la práctica. Hagamos una aplicación de ejemplo. @@ -334,7 +334,7 @@ Refactoriza `fastestCar` utilizando `compose()` y otras funciones en estilo poin {% initial src="./exercises/ch05/exercise_c.js#L4;" %} ```js const fastestCar = (cars) => { - const sorted = sortBy(car => car.horsepower); + const sorted = sortBy(car => car.horsepower, cars); const fastest = last(sorted); return concat(fastest.name, ' is the fastest'); }; diff --git a/ch05.md b/ch05.md index 646dae7..7a3123a 100644 --- a/ch05.md +++ b/ch05.md @@ -335,7 +335,7 @@ Refactor `fastestCar` using `compose()` and other functions in pointfree-style. {% initial src="./exercises/ch05/exercise_c.js#L4;" %} ```js const fastestCar = (cars) => { - const sorted = sortBy(car => car.horsepower); + const sorted = sortBy(car => car.horsepower, cars); const fastest = last(sorted); return concat(fastest.name, ' is the fastest'); }; diff --git a/ch06-es.md b/ch06-es.md index 39deaf4..d68652c 100644 --- a/ch06-es.md +++ b/ch06-es.md @@ -2,13 +2,13 @@ ## Programación Declarativa -Vamos a cambiar nuestra mentalidad. A partir de ahora, dejaremos de decirle al ordenador cómo hacer su trabajo y, en cambio, escribiremos una especificación de lo que nos gustaría obtener como resultado. Estoy seguro de que lo encontrarás mucho menos estresante que intentar microgestionarlo todo continuamente. +Vamos a cambiar nuestra mentalidad. A partir de ahora, dejaremos de decirle al ordenador cómo hacer su trabajo y, en cambio, escribiremos una especificación de lo que nos gustaría obtener como resultado. Estoy seguro que lo encontrarás mucho menos estresante que intentar microgestionarlo todo continuamente. Declarativo, al contrario que imperativo, significa que escribiremos expresiones en lugar de instrucciones paso a paso. -Piensa en SQL. No existe un "primero haz esto, luego haz lo otro". Existe una expresión que especifica lo que nos gustaría obtener de la base de datos. Nosotros no decidimos como hacer el trabajo, la base de datos lo decide. Cuando se actualiza la base de datos y el motor de SQL es optimizado, nosotros no tenemos que cambiar nuestra consulta. Se debe a que existen muchas maneras de interpretar nuestra especificación y conseguir el mismo resultado. +Piensa en SQL. No existe un "primero haz esto, luego haz lo otro". Existe una expresión que especifica lo que nos gustaría obtener de la base de datos. No decidimos como hacer el trabajo, es la base de datos quien lo decide. Cuando se actualiza la base de datos y el motor de SQL es optimizado, no tenemos que cambiar nuestra consulta. Esto se debe a que existen muchas maneras de interpretar nuestra especificación y conseguir el mismo resultado. -Para algunas personas, yo incluido, al principio cuesta comprender el concepto de la programación declarativa, así que vamos a mostrar algunos ejemplos para hacernos una idea. +Para algunas personas, yo incluido, cuesta entender de primeras el concepto de programación declarativa, así que vamos a mostrar algunos ejemplos para hacernos una idea. ```js // imperativo @@ -27,7 +27,7 @@ La versión con `map` es una sola expresión. No requiere ningún orden de evalu Además de ser más clara y más concisa, la función `map` puede ser optimizada a voluntad sin que el valioso código de nuestra aplicación necesite cambiar. -Para aquellos que estén pensando "Sí, pero es mucho más rápido hacer el bucle imperativo", les sugiero que se informen sobre cómo el JIT optimiza su código. Aquí hay un [excelente video que puede arrojar algo de luz](https://www.youtube.com/watch?v=g0ek4vV7nEA). +Para quien esté pensando "Sí, pero es mucho más rápido hacer el bucle imperativo", les sugiero que se informen sobre cómo el JIT optimiza su código. Aquí hay un [excelente video que puede arrojar algo de luz](https://www.youtube.com/watch?v=g0ek4vV7nEA). He aquí otro ejemplo. @@ -42,7 +42,7 @@ const authenticate = (form) => { const authenticate = compose(logIn, toUser); ``` -Aunque no hay nada necesariamente malo en la versión imperativa, sigue escondiendo una evaluación por pasos. La expresión con `compose` simplemente afirma un hecho: la autenticación es la composición de `toUser` y `logIn`. Nuevamente, esto deja margen de maniobra para permitir cambios en el código, y hace que nuestro código de aplicación sea una especificación de alto nivel. +Aunque no hay nada necesariamente malo en la versión imperativa, esta sigue escondiendo una evaluación por pasos. La expresión con `compose` simplemente afirma un hecho: la autenticación es la composición de `toUser` y `logIn`. Nuevamente, esto deja margen de maniobra para permitir cambios en el código, y hace que nuestro código de aplicación sea una especificación de alto nivel. Como no tenemos que codificar el orden de evaluación, la programación declarativa se presta a la computación paralela. Esto, junto con las funciones puras, es la razón por la que la programación funcional es una buena opción para el futuro paralelo; en realidad no tenemos que hacer nada especial para conseguir sistemas paralelos/concurrentes. @@ -87,7 +87,7 @@ Ahora que hemos dejado esto claro, vamos a la especificación. Nuestra aplicaci 3. Transformar el json resultante en imágenes html 4. Colocarlas en la pantalla -Arriba se mencionan 2 acciones impuras. ¿Puedes verlas? Esos pedacitos dónde se obtienen datos de la api de flickr y donde se muestran en la pantalla. Definámoslos primero para así poder ponerlos en cuarentena. Además, añadiré nuestra bonita función `trace` para poder depurar fácilmente. +Arriba se mencionan 2 acciones impuras. ¿Puedes verlas? Esos pedacitos donde se obtienen datos de la api de flickr y donde se muestran en la pantalla. Definámoslos primero para así poder ponerlos en cuarentena. Además, añadiré nuestra bonita función `trace` para poder depurar fácilmente. ```js const Impure = { @@ -170,7 +170,7 @@ Mira eso. Una especificación hermosamente declarativa de lo que son las cosas, ## Una Refactorización Basada En Principios -Hay una optimización disponible; nosotros hacemos `map` sobre cada elemento para convertirlo en una url, luego hacemos nuevamente `map` sobre esas `mediaUrls` para convertirlas en etiquetas `img`. He aquí una ley con respecto a map y la composición: +Hay una optimización disponible; hacemos `map` sobre cada elemento para convertirlo en una url, luego hacemos nuevamente `map` sobre esas `mediaUrls` para convertirlas en etiquetas `img`. He aquí una ley con respecto a map y la composición: ```js // ley de composición de map @@ -215,6 +215,6 @@ const images = compose(map(mediaToImg), prop('items')); ## En Resumen -Hemos visto con una aplicación pequeña, pero real, como poner en práctica nuestras nuevas habilidades. Hemos utilizado nuestro marco matemático para razonar sobre nuestro código y refactorizarlo. Pero ¿qué hay del manejo de errores y la ramificación de código? ¿Cómo podemos hacer que toda la aplicación sea completamente pura en vez de tan solo agregar a un espacio de nombres las funciones destructivas? ¿Cómo podemos hacer que nuestra aplicación sea más segura y expresiva? Estas son las preguntas que abordaremos en la parte 2. +Con una aplicación pequeña, pero real, hemos visto como poner en práctica nuestras nuevas habilidades. Hemos utilizado nuestro marco matemático para razonar sobre nuestro código y refactorizarlo. Pero ¿qué hay del manejo de errores y la ramificación de código? ¿Cómo podemos hacer que toda la aplicación sea completamente pura en vez de tan solo agregar a un espacio de nombres las funciones destructivas? ¿Cómo podemos hacer que nuestra aplicación sea más segura y expresiva? Estas son las preguntas que abordaremos en la parte 2. [Capítulo 7: Hindley-Milner y Yo](ch07-es.md) diff --git a/ch07-es.md b/ch07-es.md index 9f58c24..819c948 100644 --- a/ch07-es.md +++ b/ch07-es.md @@ -1,9 +1,9 @@ # Capítulo 07: Hindley-Milner y Yo ## ¿Cuál Es Tu Tipo? -Si tu llegada al mundo funcional es reciente, no tardarás en verte de firmas de tipos hasta las rodillas. Los tipos son el meta lenguaje que permite a personas de todos los ámbitos comunicarse de forma sucinta y eficaz. La mayoría de las veces están escritas en un sistema llamado "Hindley-Milner" que examinaremos juntos en este capítulo. +Si tu llegada al mundo funcional es reciente, no tardarás en verte de firmas de tipo hasta las rodillas. Los tipos son el meta lenguaje que permite a personas de todos los ámbitos comunicarse de forma sucinta y eficaz. La mayoría de las veces están escritas en un sistema llamado "Hindley-Milner" que examinaremos juntos en este capítulo. -Cuando trabajamos con funciones puras, las firmas de tipos tienen un poder expresivo que el inglés no puede lograr [*ni el español*]. Estas firmas te susurran al oído los íntimos secretos de una función. En una única y compacta línea exponen comportamiento e intención. Podemos derivar "teoremas gratuitos" de ellas. Los tipos pueden ser inferidos, así que no hay necesidad de anotaciones de tipo explícitas. Pueden afinarse al detalle o dejarse como algo general y abstracto. No solo son útiles para comprobaciones en tiempo de compilación, sino que resultan ser la mejor documentación posible disponible. Las firmas de tipos juegan, por tanto, un papel importante en la programación funcional; mucho más de lo que cabría esperar en un principio. +Cuando trabajamos con funciones puras, las firmas de tipo tienen un poder expresivo que el inglés no puede lograr [*ni el español*]. Estas firmas te susurran al oído los íntimos secretos de una función. En una única y compacta línea exponen comportamiento e intención. Podemos derivar "teoremas gratuitos" de ellas. Los tipos pueden ser inferidos, así que no hay necesidad de anotaciones de tipo explícitas. Pueden afinarse al detalle o dejarse como algo general y abstracto. No solo son útiles para comprobaciones en tiempo de compilación, sino que resultan ser la mejor documentación posible disponible. Las firmas de tipos juegan, por tanto, un papel importante en la programación funcional; mucho más de lo que cabría esperar en un principio. JavaScript es un lenguaje dinámico, pero eso no significa que evitemos los tipos por completo. Seguimos trabajando con strings, números, booleanos, etc. Es solo que no hay una integración a nivel de lenguaje, por lo que mantenemos esta información en nuestras cabezas. No hay de que preocuparse, podemos utilizar comentarios para servir a nuestro propósito dado que usamos las firmas como documentación. diff --git a/ch08-es.md b/ch08-es.md index 13201d1..dc5f238 100644 --- a/ch08-es.md +++ b/ch08-es.md @@ -4,9 +4,9 @@ http://blog.dwinegar.com/2011/06/another-jar.html -Hemos visto como escribir programas que canalicen los datos a través de una serie de funciones puras. Son especificaciones declarativas de comportamiento. Pero ¡¿qué pasa con el control de flujo, el manejo de errores, las acciones asíncronas, el estado y me atrevo a decir, con los efectos?! En este capítulo, descubriremos los cimientos sobre los que se construyen todas estas abstracciones tan útiles. +Hemos visto como escribir programas que canalicen los datos a través de una serie de funciones puras. Son especificaciones declarativas de comportamiento. Pero ¡¿qué pasa con el control de flujo, el manejo de errores, las acciones asíncronas, el estado y me atrevo a decir, los efectos?! En este capítulo, descubriremos los cimientos sobre los que se construyen todas estas abstracciones tan útiles. -Primero crearemos un contenedor. Este contenedor debe contener cualquier tipo de valor; una bolsa de cierre zip que solo acepta pudin de tapioca raramente es útil. Será un objeto, pero no le daremos métodos ni propiedades en el sentido de la orientación de objetos. No, lo trataremos como si de un cofre del tesoro se tratara; una caja especial que envuelve a nuestros valiosos datos. +Primero crearemos un contenedor. Este contenedor debe poder contener cualquier tipo de valor; una bolsa de cierre zip que solo acepta pudin de tapioca raramente es útil. Será un objeto, pero no le daremos métodos ni propiedades en el sentido de la orientación de objetos. No, lo trataremos como si de un cofre del tesoro se tratara; una caja especial que envuelve a nuestros valiosos datos. ```js class Container { @@ -43,7 +43,7 @@ Aclaremos algunas cosas antes de continuar: * El valor no puede ser de un tipo específico o de lo contrario difícilmente nuestro `Container` haría honor a su nombre. -* Una vez los datos entran en nuestro `Container` se quedan ahí. Nosotros *podríamos* sacarlos usando `.$value`, pero eso anularía el propósito. +* Una vez los datos entran en nuestro `Container` se quedan ahí. *Podríamos* sacarlos usando `.$value`, pero eso anularía su propósito. Las razones por las cuales estamos haciendo esto quedarán claras como un tarro de cristal, pero por el momento, tendréis que ser pacientes. @@ -75,11 +75,11 @@ Podemos trabajar con nuestro valor sin salir del contenedor. Esto es algo extrao Espera un minuto, si seguimos llamando a `map`, ¡parece ser una especie de composición! ¿Qué magia matemática es esta? Bien amigos, acabamos de descubrir los *Funtores*. -> Un Funtor es un tipo que implementa `map` y obedece algunas leyes +> Un Funtor es un tipo que implementa `map` y obedece a algunas leyes Sí, *Funtor* es simplemente una interfaz con un contrato. También podríamos haberlo llamado *Mapeable*, pero entonces, ¿dónde estaría la diversión? Los Funtores provienen de la teoría de categorías y veremos las matemáticas en detalle hacia el final del capítulo, pero por ahora, trabajemos en la intuición y los usos prácticos de esta interfaz de nombre extraño. -¿Qué razón podríamos tener para embotellar un valor y utilizar `map` para llegar a él? La respuesta se revela por si sola si escogemos mejor la pregunta: ¿Qué ganamos al pedir a nuestro contenedor que aplique funciones por nosotros? Pues la abstracción de la aplicación de funciones. Cuando aplicamos una función mediante `map`, le pedimos al tipo del contenedor que la ejecute por nosotros. Este es, de hecho, un concepto muy poderoso. +¿Qué razón podríamos tener para embotellar un valor y utilizar `map` para llegar a él? La respuesta se revela por si sola si escogemos mejor la pregunta: ¿Qué ganamos al pedir a nuestro contenedor que aplique funciones en nuestro lugar? Pues la abstracción de la aplicación de funciones. Cuando aplicamos una función mediante `map`, le pedimos al tipo del contenedor que la ejecute de nuestra parte. Este es, de hecho, un concepto muy poderoso. ## El Maybe de Schrödinger @@ -113,7 +113,7 @@ class Maybe { } ``` -Ahora, `Maybe` se parece mucho a `Container` pero con un pequeño cambio: comprueba si tiene un valor antes de llamar a la función proporcionada. Esto tiene el efecto de evitar esos molestos nulls mientras aplicamos `map` (Ten en cuenta que esta implementación está simplificada para la enseñanza). +Ahora, `Maybe` se parece mucho a `Container` pero con un pequeño cambio: comprueba si tiene un valor antes de llamar a la función proporcionada. Esto tiene el efecto de evitar esos molestos nulls cuando aplicamos `map` (Ten en cuenta que esta implementación está simplificada por motivos didácticos). ```js Maybe.of('Malkovich Malkovich').map(match(/a/ig)); @@ -138,7 +138,7 @@ Esta sintaxis con el punto está bien y es funcional, pero por las razones menci const map = curry((f, anyFunctor) => anyFunctor.map(f)); ``` -Esto es perfecto, ya que podemos continuar con la composición como de costumbre y `map` funcionará según lo esperado. Este también es el caso con el `map` de ramda. Usaremos la sintaxis con el punto cuando sea instructiva y la versión *pointfree* cuando sea conveniente. ¿Te has dado cuenta? He introducido disimuladamente una notación adicional en nuestra firma de tipos. `Functor f =>` nos dice que `f` debe ser un Funtor. No es tan difícil, pero sentí que debía mencionarlo. +Esto es perfecto, ya que podemos continuar con la composición como de costumbre y `map` funcionará según lo esperado. Este también es el caso con el `map` de ramda. Usaremos la sintaxis con el punto cuando sea instructiva y la versión *pointfree* cuando sea conveniente. ¿Te has dado cuenta? He introducido disimuladamente una notación adicional en nuestra firma de tipos. `Functor f =>` nos dice que `f` debe ser un Funtor. Aunque no es algo que sea complicado, sentí que debía mencionarlo. ## Casos de Uso @@ -286,7 +286,7 @@ left('rolls eyes...').map(prop('host')); `Left` es como un adolescente e ignora nuestra solicitud de aplicarse `map` a sí mismo. `Right` funcionará igual que `Container` (también conocido como Identidad). El poder viene de la capacidad de incrustar un mensaje de error dentro de `Left`. -Supón que tenemos una función que podría no tener éxito. Quizás una función que calcule la edad a partir de una fecha de nacimiento. Podemos usar `Nothing` para avisar del fracaso y bifurcar nuestro programa, sin embargo, eso no nos dice mucho. Quizás nos gustaría saber por qué ha fallado. Escribámoslo usando `Either`. +Supón que tenemos una función que podría no tener éxito. Quizás una función que calcule la edad a partir de una fecha de nacimiento. Podríamos utilizar `Nothing` para avisar del fracaso y bifurcar nuestro programa, sin embargo, eso no nos dice mucho. Quizás nos gustaría saber por qué ha fallado. Escribámoslo usando `Either`. ```js const moment = require('moment'); @@ -326,13 +326,13 @@ zoltar({ birthDate: 'balloons!' }); Cuando `birthdate` es válido, el programa muestra su mística predicción en la pantalla para que la contemplemos. De lo contrario, se nos entrega un `Left` con el mensaje de error tan claro como el día, aunque todavía escondido en su contenedor. Esto actúa igual que si hubiésemos lanzado un error, pero de una manera más tranquila y suave en lugar de perder los estribos y gritar como un niño cuando algo sale mal. -En este ejemplo, estamos haciendo una bifurcación lógica en nuestro flujo de control dependiendo de la validez de la fecha de cumpleaños, leyéndose en una sola línea de derecha a izquierda en vez de escalar a través de las llaves de una declaración condicional. Normalmente, moveríamos el `console.log` fuera de la función `zoltar` y aplicaríamos `map` al momento de llamarla, pero es útil ver como la rama del `Right` diverge. Para esto usamos `_` en la firma de la rama derecha para indicar que es un valor que debe ser ignorado (En algunos navegadores debes usar `console.log.bind(console)` para usarlo como función de primera clase). +En este ejemplo, estamos haciendo una bifurcación lógica en nuestro flujo de control dependiendo de la validez de la fecha de cumpleaños, leyéndose en una sola línea de derecha a izquierda en vez de escalar a través de las llaves de una declaración condicional. Normalmente, moveríamos el `console.log` fuera de la función `zoltar` y aplicaríamos `map` al momento de llamarla, pero es útil ver como la rama del `Right` diverge. Utilizamos `_` en la firma de la rama derecha para indicar que es un valor que debe ser ignorado (En algunos navegadores debes usar `console.log.bind(console)` para usarlo como función de primera clase). Me gustaría aprovechar esta oportunidad para resaltar algo que podrías haber pasado por alto: `fortune`, a pesar de su uso con `Either` en este ejemplo, es completamente ignorante de cualquier funtor que tenga a su alrededor. Este era también el caso para `finishTransaction` en el ejemplo anterior. En el momento de la llamada, una función puede ser rodeada por `map`, lo que la transforma, en términos informales, de no ser un funtor a serlo. Llamamos a este proceso *levantamiento* (*lifting*). Es mejor que las funciones trabajen con tipos de datos normales que con tipos contenedor, para luego ser levantadas al contenedor apropiado según se considere necesario. Esto conduce a funciones más simples y reutilizables que pueden ser alteradas a demanda para trabajar con cualquier funtor. `Either` es genial para errores casuales como validaciones y también para casos más graves como archivos faltantes o sockets rotos. Prueba a reemplazar por `Either` alguno de los ejemplos de `Maybe` para obtener un mejor entendimiento. -Por otro lado, no puedo dejar de pensar que le he hecho un flaco favor a `Either` presentándolo como un simple contenedor de mensajes de error. `Either` captura la disyunción lógica (||) en un tipo. También codifica la idea de un *Coproducto* de la teoría de categorías, que no se verá en este libro, aunque vale la pena leer sobre ello, ya que contiene varias propiedades a explotar. `Either` es la suma canónica de tipos (o unión disjunta de conjuntos) porque la cantidad total de posibles habitantes es la suma de los dos tipos contenidos (sé que esto suena a magia por lo que acá les entrego un [gran artículo](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/sum-types)). Hay muchas cosas que `Either` puede ser, pero como funtor, se usa por su manejo de errores. +Por otro lado, no puedo dejar de pensar que le he hecho un flaco favor a `Either` presentándolo como un simple contenedor de mensajes de error. `Either` captura la disyunción lógica (||) en un tipo. También codifica la idea de *Coproducto* de la teoría de categorías, que no se verá en este libro, aunque vale la pena leer sobre ello, ya que contiene varias propiedades a explotar. `Either` es la suma de tipos canónica (o unión disjunta de conjuntos) porque la cantidad total de posibles habitantes es la suma de los dos tipos contenidos (sé que esto suena a magia por lo que acá les entrego un [gran artículo](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/sum-types)). Hay muchas cosas que `Either` puede ser, pero como funtor, se usa por su manejo de errores. Igual que con `Maybe`, tenemos a la pequeña `either`, que se comporta de manera similar, pero toma dos funciones en lugar de una y un valor estático. Cada función debe devolver el mismo tipo. @@ -368,7 +368,7 @@ zoltar({ birthDate: 'balloons!' }); // undefined ``` -Por fin, un uso para esa misteriosa función `id`. Simplemente repite como un loro el valor de `Left` para pasar el mensaje de error a `console.log`. Hemos hecho que nuestra app de videncia sea más robusta imponiendo el manejo de error desde dentro de `getAge`. O bien abofeteamos al usuario con la dura verdad o bien continuamos con nuestro proceso. Y con esto, podemos pasar a otro tipo de funtor completamente diferente. +Por fin, un uso para esa misteriosa función `id`. Simplemente repite como un loro el valor de `Left` para pasar el mensaje de error a `console.log`. Hemos hecho que nuestra app de videncia sea más robusta imponiendo el manejo de errores desde dentro de `getAge`. O bien abofeteamos al usuario con la dura verdad o bien continuamos con nuestro proceso. Y con esto, podemos pasar a otro tipo de funtor completamente diferente. ## El Viejo McDonald Tenía Efectos... @@ -430,7 +430,7 @@ $('#myDiv').map(head).map(div => div.innerHTML); // IO('I am some inner html') ``` -Aquí, `ioWindow` es un `IO` al que podemos aplicar `map` directamente, mientras que `$` es una función que devuelve un `IO` después ser llamada. He escrito los valores de retorno *conceptuales* para expresar mejor el `IO`, aunque, en realidad, siempre será `{ $value: [Function] }`. Cuando aplicamos `map` sobre nuestro `IO`, ponemos esta función al final de una composición que, a su vez, se convierte en el nuevo `$value` y así sucesivamente. Nuestras funciones mapeadas no se ejecutan, sino que quedan adheridas al final del cálculo que estamos construyendo, función a función, como si cuidadosamente colocáramos fichas de dominó que no nos atrevemos a volcar. El resultado recuerda al patrón comando del Gang of Four o a una cola. +Aquí, `ioWindow` es un `IO` al que podemos aplicar `map` directamente, mientras que `$` es una función que devuelve un `IO` al ser llamada. He escrito los valores de retorno *conceptuales* para expresar mejor el `IO`, aunque, en realidad, siempre será `{ $value: [Function] }`. Cuando aplicamos `map` sobre nuestro `IO`, ponemos esta función al final de una composición que, a su vez, se convierte en el nuevo `$value` y así sucesivamente. Nuestras funciones mapeadas no se ejecutan, sino que quedan adheridas al final del cálculo que estamos construyendo, función a función, como si cuidadosamente colocáramos fichas de dominó que no nos atrevemos a volcar. El resultado recuerda al patrón comando del Gang of Four o a una cola. Tómate un momento para trabajar tu intuición sobre los funtores. Si miramos más allá de los detalles de implementación, deberíamos sentirnos como en casa mapeando cualquier contenedor, sin importar sus peculiaridades o idiosincrasias. Tenemos que agradecer este poder pseudopsíquico a las leyes de funtores, las cuales exploraremos hacia el final del capítulo. En cualquier caso, por fin podemos jugar con valores impuros sin sacrificar nuestra preciosa pureza. @@ -522,7 +522,7 @@ Si conoces las promesas, puede que reconozcas a la función `map` como `then`, c Al igual que `IO`, `Task` esperará pacientemente a que le demos luz verde antes de ejecutarse. De hecho, dado que espera nuestra orden, `IO` está efectivamente subsumido en `Task` para todas las cosas asíncronas; `readFile` y `getJSON` no requieren un contenedor `IO` adicional para ser puros. Es más, `Task` funciona de manera similar cuando `map`eamos sobre él: estamos colocando instrucciones para el futuro igual que si colocáramos una tabla de tareas en una cápsula del tiempo; un acto de sofisticada procrastinación tecnológica. -Para ejecutar nuestra tarea, debemos llamar al método `fork`. Esto funciona como `unsafePerformIO`, pero como su nombre indica, bifurcará nuestro proceso y la evaluación continuará sin bloquear nuestro hilo de ejecución. Esto puede ser implementado de numerosas maneras con hilos y demás, pero aquí actuará como lo haría una llamada asíncrona normal, así que la gran rueda del bucle de eventos de JavaScript seguirá girando. Echemos un vistazo a `fork`: +Para ejecutar nuestra tarea [*Task*], debemos llamar al método `fork`. Esto funciona como `unsafePerformIO`, pero como su nombre indica, bifurcará nuestro proceso y la evaluación continuará sin bloquear nuestro hilo de ejecución. Esto puede ser implementado de numerosas maneras con hilos y demás, pero aquí actuará como lo haría una llamada asíncrona normal y la gran rueda del bucle de eventos de JavaScript seguirá girando. Echemos un vistazo a `fork`: ```js // -- Aplicación pura ------------------------------------------------- @@ -588,7 +588,7 @@ En este ejemplo, todavía hacemos uso de `Either` e `IO` desde la rama exitosa d Podría continuar, pero eso es todo. Tan simple como `map`. -En la práctica, es probable que tengas múltiples tareas asíncronas en un solo flujo de trabajo y aún no hemos adquirido todas las API de los contenedores como para afrontar este escenario. No te preocupes, pronto veremos cosas sobre mónadas y demás, pero primero debemos examinar las matemáticas que hacen que todo esto sea posible. +En la práctica, es probable que tengas múltiples tareas asíncronas en un solo flujo de trabajo y aún no hemos adquirido todas las API de contenedores como para afrontar este escenario. No te preocupes, pronto veremos cosas sobre mónadas y demás, pero primero debemos examinar las matemáticas que hacen que todo esto sea posible. ## Un Poco de Teoría @@ -629,13 +629,13 @@ Tal vez nuestra definición de categoría sea todavía algo confusa. Puedes pens Categorías mapeadas -Por ejemplo, `Maybe` mapea nuestra categoría de tipos y funciones a una categoría donde cada objeto puede no existir y cada morfismo tiene una comprobación de `null`. Logramos esto en el código rodeando cada función con `map` y cada tipo con nuestro funtor. Sabemos que cada uno de nuestros tipos normales y cada una de nuestras funciones seguirán pudiéndose componer en este nuevo mundo. Técnicamente, cada funtor en nuestro código mapea a una subcategoría de tipos y funciones que hace que todos los funtores sean de un tipo en particular llamado endofuntor, pero para nuestros propósitos consideraremos que son de otra categoría. +Por ejemplo, `Maybe` mapea nuestra categoría de tipos y funciones a una categoría donde cada objeto puede no existir y cada morfismo tiene una comprobación de `null`. Logramos esto en el código rodeando a cada función con `map` y a cada tipo con nuestro funtor. Sabemos que cada uno de nuestros tipos normales y cada una de nuestras funciones seguirán pudiéndose componer en este nuevo mundo. Técnicamente, cada funtor en nuestro código mapea a una subcategoría de tipos y funciones que hace que todos los funtores sean de un tipo en particular llamado endofuntor, pero para nuestros propósitos consideraremos que son de otra categoría. Podemos visualizar el mapeo de morfismos y sus correspondientes objetos mediante este diagrama: diagrama de funtor -Además de visualizar el morfismo mapeado de una categoría a otra bajo el funtor `F`, vemos que el diagrama conmuta, es decir, si se siguen las flechas cada ruta produce el mismo resultado. Las distintas rutas significan diferentes comportamientos, pero siempre acabamos en el mismo tipo. Este formalismo nos permite basarnos en principios a la hora de razonar sobre nuestro código; podemos aplicar fórmulas audazmente sin tener que interpretar y examinar cada escenario individualmente. Veamos esto en un ejemplo concreto: +Además de visualizar el morfismo mapeado de una categoría a otra bajo el funtor `F`, vemos que el diagrama conmuta, es decir, si se siguen las flechas cada ruta conduce al mismo resultado. Las distintas rutas significan diferentes comportamientos, pero siempre acabamos en el mismo tipo. Este formalismo nos permite basarnos en principios a la hora de razonar sobre nuestro código; podemos aplicar fórmulas audazmente sin tener que interpretar y examinar cada escenario individualmente. Veamos esto en un ejemplo concreto: ```js // topRoute :: String -> Maybe String @@ -691,7 +691,7 @@ ctmd2.getCompose; // Task(Just('Rock over London, rock on, Chicago')) ``` -Ahí lo tienes, un solo `map`. La composición de funtores es asociativa y anteriormente definimos `Container` que en realidad se llama funtor `Identidad`. Si tenemos identidad y composición asociativa tenemos una categoría. Esta categoría en particular tiene categorías como objetos y funtores como morfismos, lo que es suficiente para hacer que a uno le transpire el cerebro. No profundizaremos demasiado en esto, pero es bueno apreciar las implicaciones arquitectónicas o incluso solo la simple belleza abstracta en el patrón. +Ahí lo tienes, un solo `map`. La composición de funtores es asociativa y anteriormente definimos `Container` que en realidad se llama funtor `Identidad`. Si tenemos identidad y composición asociativa tenemos una categoría. Esta categoría en particular tiene categorías como objetos y funtores como morfismos, lo que es suficiente para hacer que a uno le transpire el cerebro. No profundizaremos demasiado en esto, pero es bueno apreciar las implicaciones arquitectónicas o incluso tan solo la simple belleza abstracta en el patrón. ## En Resumen @@ -746,7 +746,7 @@ const initial = undefined; --- -Dada la siguiente función de soporte: +Dada las siguientes funciones de soporte: ```js // showWelcome :: User -> String diff --git a/ch09-es.md b/ch09-es.md index fed1e5f..3f0cae1 100644 --- a/ch09-es.md +++ b/ch09-es.md @@ -1,6 +1,6 @@ # Capítulo 09: Cebollas Monádicas -## Factoría de Funtores Punzantes +## Fábrica de Funtores Puntiagudos Antes de seguir avanzando, tengo algo que confesar: No he sido completamente honesto sobre ese método `of` que hemos colocado en cada uno de nuestros tipos. Resulta que no está ahí para evitar la palabra clave `new`, si no para colocar los valores en lo que se llama *contexto mínimo por defecto*. Sí, `of` no sustituye a un constructor, sino que forma parte de una importante interfaz a la que llamamos *Pointed*. @@ -24,7 +24,7 @@ Either.of('The past, present and future walk into a bar...').map(concat('it was Si recuerdas, los constructores de `IO` y `Task` esperan una función como argumento, pero `Maybe` y `Either` no. La motivación para esta interfaz es tener una forma común y consistente de colocar un valor en nuestro funtor sin las complejidades y demandas específicas de cada constructor. El término "contexto mínimo por defecto" carece de precisión, pero recoge bien la idea: nos gustaría levantar cualquier valor dentro de nuestro tipo y aplicarle `map` como de costumbre, obteniendo el comportamiento esperado de cualquier funtor. -Una corrección importante que debo hacer llegados a este punto (el juego de palabras es intencionado), es que `Left.of` no tiene ningún sentido. Cada funtor debe tener una forma de colocarle dentro un valor y en `Either` eso se hace con `new Right(x)`. Definimos `of` usando `Right` porque si nuestro tipo *puede* aplicar `map`, debe aplicar `map`. Viendo los ejemplos anteriores, deberíamos intuir como funcionará `of` normalmente y `Left` rompe ese molde. +Una corrección importante que debo hacer llegados a este punto, es que `Left.of` no tiene ningún sentido. Cada funtor debe tener una forma de colocarle dentro un valor y en `Either` eso se hace con `new Right(x)`. Definimos `of` usando `Right` porque si nuestro tipo *puede* aplicar `map`, debe aplicar `map`. Viendo los ejemplos anteriores, deberíamos intuir como funcionará `of` normalmente y `Left` rompe ese molde. Es posible que hayas oído hablar de funciones como `pure`, `point`, `unit`, y `return`. Estos son varios alias para nuestro método `of`, la función internacional del misterio. `of` será importante cuando empecemos a usar mónadas porque, como veremos, es nuestra responsabilidad volver a colocar los valores en el tipo manualmente. @@ -56,7 +56,7 @@ cat('.git/config'); // IO(IO('[core]\nrepositoryformatversion = 0\n')) ``` -Lo que tenemos aquí es un `IO` atrapado dentro de otro `IO` porque `print` introdujo un segundo `IO` al aplicarlo con `map`. Para seguir trabajando con nuestro string, debemos hacer `map(map(f))` y para ver el efecto debemos hacer `unsafePerformIO().unsafePerformIO()`. +Lo que hemos obtenido es un `IO` atrapado dentro de otro `IO` porque `print` introdujo un segundo `IO` al aplicarla con `map`. Para seguir trabajando con nuestro string, debemos hacer `map(map(f))` y para ver el efecto debemos hacer `unsafePerformIO().unsafePerformIO()`. ```js // cat :: String -> IO (IO String) @@ -91,9 +91,9 @@ firstAddressStreet({ // Maybe(Maybe(Maybe({name: 'Mulburry', number: 8402}))) ``` -De nuevo vemos esta situación en la que tenemos funtores anidados donde se puede ver claramente que hay tres posibilidades de fallo en nuestra función, pero es un poco presuntuoso esperar que quien nos llama aplicará `map` tres veces para llegar al valor, que acabamos de conocer. Este patrón aparecerá una y otra vez y es la principal situación por la que necesitaremos hacer brillar en el cielo nocturno al poderoso símbolo de la mónada. +De nuevo vemos esta situación en la que tenemos funtores anidados donde es bueno poder ver que hay tres posibilidades de fallo en nuestra función, pero es un poco presuntuoso suponer que quien nos llame va a aplicar `map` tres veces para llegar al valor; acabamos de conocernos. Este patrón aparecerá una y otra vez y es la razón principal por la que necesitaremos hacer brillar en el cielo nocturno el poderoso símbolo de la mónada. -He dicho que las mónadas son como cebollas porque se nos saltan las lágrimas cuando pelamos con `map` cada capa de funtor anidado para llegar al valor del interior. Podemos secar nuestros ojos, respirar hondo, y utilizar un método llamado `join`. +He dicho que las mónadas son como cebollas porque se nos saltan las lágrimas cuando pelamos con `map` cada capa de funtor anidado para llegar al valor del interior. Podemos secar nuestros ojos, respirar hondo, y utilizar un método llamado `join` [*unir*]. ```js const mmo = Maybe.of(Maybe.of('nunchucks')); @@ -115,7 +115,7 @@ ttt.join(); // Task(Task('sewers')) ``` -Si tenemos dos capas del mismo tipo, podemos unirlas aplastándolas juntas con `join`. Esta capacidad de unir, este matrimonio de funtores, es lo que hace mónada a una mónada. Avancemos hacia la definición completa con algo un poco más preciso: +Si tenemos dos capas del mismo tipo, podemos unirlas aplastándo la una con la otra mediante `join`. Esta capacidad de unir, este matrimonio de funtores, es lo que hace mónada a una mónada. Avancemos hacia la definición completa con algo un poco más preciso: > Las mónadas son funtores pointed que pueden aplanar @@ -127,7 +127,7 @@ Maybe.prototype.join = function join() { }; ``` -Ahí está, tan simple como absorber a nuestro propio gemelo en el vientre. Si tenemos `Maybe(Maybe(x))` entonces `.$value` simplemente eliminará la capa adicional innecesaria y, a partir de ahí, podremos aplicar `map` con seguridad. De lo contrario, solo tendremos el `Maybe` ya que no se habría mapeado nada en primer lugar. +Ahí está, tan simple como absorber a nuestro propio gemelo en el vientre. Si tenemos `Maybe(Maybe(x))` entonces `.$value` simplemente eliminará la innecesaria capa adicional y a partir de ahí podremos aplicar `map` con seguridad. De lo contrario, solo tendremos el `Maybe` ya que no se habría mapeado nada en primer lugar. Ahora que tenemos un método `join`, vamos a espolvorear algo de polvo de mónada mágica sobre el ejemplo de `firstAddressStreet` y a verlo en acción: @@ -155,7 +155,7 @@ Hemos añadido `join` allá donde nos hemos encontrado `Maybe`s anidados para ev IO.prototype.join = () => this.unsafePerformIO(); ``` -De nuevo, nosotros solo hemos eliminado una capa. O sea, no nos hemos deshecho de la pureza, sino que simplemente hemos eliminado una capa sobrante de embalaje. +De nuevo, solo hemos eliminado una capa. O sea, no nos hemos deshecho de la pureza, sino que simplemente hemos eliminado una capa sobrante de embalaje. ```js // log :: a -> IO a @@ -188,7 +188,7 @@ applyPreferences('preferences').unsafePerformIO(); `getItem` devuelve un `IO String` así que aplicamos `map` para parsearlo. Tanto `log` como `setStyle` devuelven `IO` por lo que hemos de aplicar `join` para mantener nuestro anidamiento bajo control. -## Mi Cadena Golpea Mi Pecho +## Mi Cadena Me Golpea El Pecho cadena @@ -272,7 +272,7 @@ Podríamos haber escrito estos ejemplos con `compose`, pero habríamos necesitad De todos modos, vamos a los ejemplos anteriores. En el primer ejemplo vemos dos tareas encadenadas en una secuencia de acciones asíncronas; primero recupera a la persona usuaria y luego con su id encuentra a sus amistades. Usamos `chain` para evitar vernos en la situación de `Task(Task([Friend]))`. -A continuación, utilizamos `querySelector` para encontrar diferentes entradas y crear un mensaje de bienvenida. Date cuenta de que en la función más interna tenemos acceso tanto a `uname` como a `email`; eso es asignación funcional de variables en su máxima expresión. Dado que `IO` nos presta amablemente su valor, tenemos la responsabilidad de dejarlo como lo encontramos, pues no querríamos corromper su veracidad (ni nuestro programa). `IO.of` es la herramienta perfecta para el trabajo y es la razón por la que Pointed es un prerrequisito importante para la interfaz Mónada. Sin embargo, podríamos optar por aplicar `map` ya que eso también devolvería el tipo correcto. +A continuación, utilizamos `querySelector` para encontrar diferentes entradas y crear un mensaje de bienvenida. Date cuenta de que en la función más interna tenemos acceso tanto a `uname` como a `email`; eso es asignación funcional de variables en su máxima expresión. Dado que `IO` nos presta amablemente su valor, tenemos la responsabilidad de dejarlo como lo encontramos, pues no querríamos corromper su credibilidad (ni nuestro programa). `IO.of` es la herramienta perfecta para el trabajo y es la razón por la que Pointed es un prerrequisito importante para la interfaz Mónada. Sin embargo, podríamos optar por aplicar `map` ya que eso también devolvería el tipo correcto. ```js querySelector('input.username').chain(({ value: uname }) => @@ -283,7 +283,7 @@ querySelector('input.username').chain(({ value: uname }) => Por último, tenemos dos ejemplos que usan `Maybe`. Dado que `chain` está usando map por debajo, si cualquier valor es nulo, detenemos en seco la computación. -No te preocupes si estos ejemplos son difíciles de entender al principio. Juega con ellos. Moléstales con un palo. Rómpelos en trozos y móntalos de nuevo. Recuerda aplicar `map` cuando lo devuelto sea un valor "normal" y `chain` cuando lo devuelto sea otro funtor. En el próximo capítulo, nos acercaremos a los `Aplicativos` y veremos buenos trucos para hacer que este tipo de expresiones sean más bonitas y altamente legibles. +No te preocupes si estos ejemplos son difíciles de entender al principio. Juega con ellos. Incórdiales con un palo. Rómpelos en trozos y únelos de nuevo. Recuerda aplicar `map` cuando lo devuelto sea un valor "normal" y `chain` cuando lo devuelto sea otro funtor. En el próximo capítulo, nos acercaremos a los `Aplicativos` y veremos buenos trucos para hacer que este tipo de expresiones sean más bonitas y altamente legibles. Como recordatorio, esto no funciona con dos tipos anidados diferentes. La composición de funtores y, posteriormente, los transformadores de mónadas, pueden ayudarnos en esa situación. @@ -291,7 +291,7 @@ Como recordatorio, esto no funciona con dos tipos anidados diferentes. La compos Programar utilizando contenedores puede llegar a ser confuso. En ocasiones nos vemos luchando por entender dentro de cuantos contenedores está un valor o si tenemos que utilizar `map` o `chain` (pronto veremos más métodos de contenedores). Podemos mejorar mucho la depuración con trucos como implementar `inspect` y aprenderemos a crear una pila [*stack*] que pueda manejar cualquier efecto que le lancemos, pero aún y así hay veces que nos preguntamos si merecen la pena tantas molestias. -Me gustaría blandir la ardiente espada monádica por un momento para exhibir el poder de programar de esta manera. +Me gustaría blandir por un momento la ardiente espada monádica para exhibir el poder de programar de esta manera. Leamos un archivo para después subirlo directamente: @@ -302,9 +302,9 @@ Leamos un archivo para después subirlo directamente: const upload = compose(map(chain(httpPost('/uploads'))), readFile); ``` -Aquí estamos bifurcando varias veces nuestro código. Mirando las firmas de tipo puedo ver que nos protegemos contra 3 errores. `readFile` utiliza `Either` para validar la entrada (quizás asegurándose de que el archivo está presente), `readFile` puede fallar cuando accede al archivo como expresa el primer parámetro de tipo de `Task`, y la subida puede fallar por cualquier razón tal y como expresa el `Error` en `httpPost`. Sin mucho esfuerzo hemos realizado con `chain` dos acciones asíncronas anidadas y secuenciales. +Aquí estamos bifurcando varias veces nuestro código. Mirando las firmas de tipo puedo ver que nos protegemos contra 3 errores. `readFile` utiliza `Either` para validar la entrada (quizás asegurándose de que el archivo está presente), `readFile` puede fallar cuando accede al archivo como expresa el primer parámetro de tipo de `Task`, y la subida puede fallar por cualquier razón tal y como expresa el `Error` en `httpPost`. Con `chain` hemos llevado a cabo dos acciones asíncronas anidadas y secuenciales sin mucho esfuerzo. -Todo esto se consigue con un solo flujo lineal de derecha a izquierda. Todo es puro y declarativo. Contiene razonamiento ecuacional y propiedades fiables. No nos vemos forzados a añadir confusos e innecesarios nombres de variables. Nuestra función `upload` está escrita con una interfaz genérica y no con una API específica de un solo uso. Es una maldita línea por dios. +Todo esto se ha conseguido con un solo flujo lineal de derecha a izquierda. Todo es puro y declarativo. Contiene razonamiento ecuacional y propiedades fiables. No nos vemos forzados a añadir confusos e innecesarios nombres de variables. Nuestra función `upload` está escrita con una interfaz genérica y no con una API específica de un solo uso. Es una maldita línea por dios. Para contrastar, veamos la forma imperativa estándar de llevar esto a cabo: @@ -336,7 +336,7 @@ La primera ley que veremos es la asociatividad, pero puede que no de la forma ac compose(join, map(join)) === compose(join, join); ``` -Estas leyes se refieren al anidamiento característico de las mónadas por lo que la asociatividad se centra en unir primero los tipos más internos o primero los más externos para llegar al mismo resultado. Una imagen puede ser más instructiva: +Estas leyes atacan al anidamiento característico de las mónadas por lo que la asociatividad se centra en unir primero los tipos más internos o primero los más externos para llegar al mismo resultado. Una imagen puede ser más instructiva: ley de la asociatividad de las mónadas @@ -372,7 +372,7 @@ mcompose(f, M) === f; mcompose(mcompose(f, g), h) === mcompose(f, mcompose(g, h)); ``` -Estas son las leyes de la categoría después de todo. Las mónadas forman una categoría llamada "categoría Kleisli" en la que todos los objetos son mónadas y los morfismos son funciones encadenadas. No pretendo burlarme de ti con trozos de teoría de categorías y sin dar mucha explicación de como encaja el rompecabezas. La intención es arañar la superficie lo suficiente como para mostrar su relevancia, y despertar cierto interés mientras nos concentramos en las propiedades prácticas que podemos usar cada día. +Estas son las leyes de la categoría después de todo. Las mónadas forman una categoría llamada "categoría Kleisli" en la que todos los objetos son mónadas y los morfismos son funciones encadenadas. No pretendo burlarme de ti con trozos de teoría de categorías sin dar mucha explicación de como encaja el rompecabezas. La intención es arañar la superficie lo suficiente como para mostrar su relevancia, y despertar cierto interés mientras nos concentramos en las propiedades prácticas que podremos usar cada día. ## En Resumen diff --git a/ch10-es.md b/ch10-es.md index 895a88e..494fbdf 100644 --- a/ch10-es.md +++ b/ch10-es.md @@ -22,13 +22,13 @@ const containerOfAdd2 = map(add, Container.of(2)); Ahora tenemos un `Container` con una función dentro que está parcialmente aplicada. Más específicamente, tenemos un `Container(add(2))` y queremos aplicar su `add(2)` al `3` de `Container(3)` para completar la llamada. En otras palabras, queremos aplicar un funtor a otro funtor. -Pues resulta que ya tenemos las herramientas para llevar a cabo está tarea. Podemos aplicar `chain` y luego `map` a la función parcialmente aplicada `add(2)`, tal que así: +Pues resulta que ya tenemos las herramientas para llevar a cabo esta tarea. Podemos aplicar `chain` y luego `map` a la función parcialmente aplicada `add(2)`, tal que así: ```js Container.of(2).chain(two => Container.of(3).map(add(two))); ``` -El problema aquí es que estamos atrapados en el mundo secuencial de las mónadas en el que nada puede ser evaluado hasta que la mónada anterior haya terminado su trabajo. Tenemos dos valores fuertes e independientes y me parece innecesario retrasar la creación de `Containter(3)` tan solo para satisfacer las demandas secuenciales de las mónadas. +El problema aquí es que estamos atrapados en el mundo secuencial de las mónadas en el que nada puede ser evaluado hasta que la mónada anterior haya terminado su trabajo. Tenemos dos valores fuertes e independientes y parece innecesario retrasar la creación de `Containter(3)` tan solo para satisfacer las demandas secuenciales de las mónadas. De hecho, si nos viésemos en este aprieto, sería maravilloso si pudiéramos, sucintamente, aplicar el contenido de un funtor al valor de otro, sin esas funciones y variables innecesarias. @@ -72,7 +72,7 @@ Percibo tu escepticismo (o quizás confusión y horror), pero mantén la mente a F.of(x).map(f) === F.of(f).ap(F.of(x)); ``` -En correcto castellano, mapear `f` equivale a usar la función `ap` de un funtor de `f`. O en un castellano más correcto, podemos colocar `x` en nuestro contendor y hacer `map(f)`, o, podemos levantar tanto `f` como `x` en nuestro contenedor y luego aplicarles `ap`. Esto nos permite escribir de izquierda a derecha: +En correcto español, mapear `f` equivale a usar la función `ap` de un funtor de `f`. O en un español más correcto, podemos colocar `x` en nuestro contendor y hacer `map(f)`, o, podemos levantar tanto `f` como `x` en nuestro contenedor y luego aplicarles `ap`. Esto nos permite escribir de izquierda a derecha: ```js Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3)); @@ -84,7 +84,7 @@ Task.of(add).ap(Task.of(2)).ap(Task.of(3)); Entrecerrando los ojos, se puede incluso reconocer vagamente la manera normal de llamar a una función. Más adelante en el capítulo veremos la versión pointfree, pero por ahora, esta es la manera preferida de escribir un código como este. Usando `of`, cada valor es transportado al mágico mundo de los contenedores, ese universo paralelo donde cada aplicación puede ser asíncrona o nula o lo que sea y donde `ap` aplicará funciones dentro de ese lugar de fantasía. Es como construir un barco dentro de una botella. -¿Has visto? Hemos utilizado `Task` en nuestro ejemplo. Esta es una de las principales situaciones donde los funtores aplicativos muestran su fuerza. Veamos un ejemplo más en profundidad. +¿Has visto? Hemos utilizado `Task` en nuestro ejemplo. Esta es una de las principales situaciones en las que los funtores aplicativos muestran su fuerza. Veamos un ejemplo más en profundidad. ## Motivación para la Coordinación @@ -101,7 +101,7 @@ Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events')); Ambas llamadas `Http` se harán a la vez y `renderPage` será llamada cuando ambas se hayan resuelto. Contrasta esto con la versión monádica en la que una tarea `Task` debe finalizar antes de que se inicie la siguiente. Dado que no necesitamos los destinos para recuperar los eventos, nos libramos de la evaluación secuencial. -De nuevo, como estamos utilizando aplicación parcial para alcanzar este resultado, debemos asegurarnos de que `renderPage` está currificada o no esperará a que terminen ambas tareas. Por cierto, si alguna vez has tenido que hacer algo así manualmente, apreciarás la asombrosa simplicidad de esta interfaz. Este es la clase de bonito código que nos acerca un paso más hacia la singularidad. +De nuevo, como estamos utilizando aplicación parcial para alcanzar este resultado, debemos asegurarnos de que `renderPage` está currificada o no esperará a que terminen ambas tareas. Por cierto, si alguna vez has tenido que hacer algo así manualmente, apreciarás la asombrosa simplicidad de esta interfaz. Esta es la bonita forma del código que nos acerca un paso más hacia la singularidad. Veamos otro ejemplo. @@ -223,19 +223,19 @@ X.prototype.ap = function ap(other) { }; ``` -Si podemos definir una mónada, podemos definir tanto la interfaz de aplicativo como de funtor. Esto es bastante notable, ya que obtenemos todos estos abrelatas sin coste alguno. Podemos incluso examinar un tipo y automatizar este proceso. +Si podemos definir una mónada, podemos definir tanto la interfaz de aplicativo como la de funtor. Esto es bastante notable, ya que obtenemos todos estos abrelatas sin coste alguno. Podemos incluso examinar un tipo y automatizar este proceso. Hay que señalar que parte del atractivo de `ap` es su capacidad para ejecutar cosas de manera concurrente, por lo que definirla mediante `chain` hace que se pierda esa optimización. A pesar de esto, es bueno tener una interfaz funcionando inmediatamente mientras uno trabaja en la mejor implementación posible. ¿Por qué no utilizar mónadas y así ya estar listos?, te preguntarás. Es una buena práctica trabajar con el nivel de potencia que necesitas en cada momento, ni más, ni menos. Al descartar posibles funcionalidades, mantenemos la carga cognitiva al mínimo. Es por esto que es bueno favorecer a los aplicativos por encima de las mónadas. -Las mónadas tienen la capacidad única de secuenciar el cálculo, asignar variables, y de detener la ejecución siguiente, todo ello gracias a la estructura de anidamiento descendente. Cuando vemos que se usan aplicativos, no tenemos que estar atentos a nada de eso. +Las mónadas tienen la capacidad única de secuenciar el cálculo, asignar variables, y detener la ejecución siguiente, todo ello gracias a la estructura de anidamiento descendente. Cuando vemos que se usan aplicativos, no tenemos que estar atentos a nada de eso. Y ahora, sobre los aspectos legales... ## Leyes -Al igual que el resto de construcciones matemáticas que hemos explorado, los funtores aplicativos tienen algunas propiedades que pueden sernos útiles en nuestro día a día programando. En primer lugar, debes saber que los aplicativos están "cerrados bajo composición", lo que significa que `ap` nunca cambiará el tipo de los contenedores por nosotros (otra razón más para favorecerlos por encima de las mónadas). Eso no quiere decir que no podamos tener múltiples efectos diferentes; podemos apilar nuestros tipos sabiendo que seguirán siendo los mismos durante toda nuestra aplicación. +Al igual que el resto de construcciones matemáticas que hemos explorado, los funtores aplicativos tienen algunas propiedades que pueden sernos útiles en nuestro día a día programando. En primer lugar, debes saber que los aplicativos están "cerrados bajo composición", lo que significa que `ap` nunca nos cambiará el tipo de los contenedores (otra razón más para favorecerlos por encima de las mónadas). Eso no quiere decir que no podamos tener múltiples efectos diferentes; podemos apilar nuestros tipos sabiendo que seguirán siendo los mismos durante toda nuestra aplicación. Para demostrarlo: @@ -323,9 +323,9 @@ IO.of(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w)); ## En Resumen -Un buen caso de uso para los aplicativos es cuando tenemos múltiples argumentos de funtor. Nos dan la posibilidad de aplicar funciones a los argumentos todo dentro del mundo de los funtores. Aunque ya podíamos hacer esto con las mónadas, preferiremos a los funtores aplicativos cuando no necesitemos ninguna funcionalidad monádica específica. +Un buen caso de uso para los aplicativos es cuando tenemos múltiples argumentos de funtor. Nos dan la posibilidad de aplicar funciones a los argumentos todo dentro del mundo de los funtores. Aunque ya podíamos hacer esto con las mónadas, preferiremos los funtores aplicativos cuando no necesitemos ninguna funcionalidad monádica específica. -Casi hemos terminado con las apis de los contenedores. Hemos aprendido a como aplicar `map`, `chain`, y ahora `ap`, a funciones. En el próximo capítulo aprenderemos a como trabajar mejor con múltiples funtores y a como desmontarlos siguiendo unos principios. +Casi hemos terminado con las apis de contenedores. Hemos aprendido a como aplicar `map`, `chain`, y ahora `ap`, a funciones. En el próximo capítulo aprenderemos a como trabajar mejor con múltiples funtores y a como desmontarlos siguiendo unos principios. [Capítulo 11: Transforma Otra Vez, Naturalmente](ch11-es.md) diff --git a/ch11-es.md b/ch11-es.md index 34b338e..09c847a 100644 --- a/ch11-es.md +++ b/ch11-es.md @@ -2,11 +2,11 @@ [*El título en inglés es 'Transform Again, Naturally' que recuerda a la canción Alone Again (Naturally) de Gilbert O'Sullivan*] -Estamos a punto de discutir sobre las *transformaciones naturales* en cuanto a su utilidad práctica en nuestro día a día programando. Sucede que son un pilar de la teoría de categorías y absolutamente indispensables a la hora de aplicar matemáticas al razonar sobre nuestro código y al refactorizarlo. Como tal, creo que es mi deber informarte sobre la lamentable injusticia que estás a punto de presenciar, indudablemente debido a mi limitado alcance. Empecemos. +Estamos a punto de dialogar sobre las *transformaciones naturales* en cuanto a su utilidad práctica en nuestro día a día programando. Sucede que son un pilar de la teoría de categorías y absolutamente indispensables a la hora de aplicar las matemáticas para razonar sobre nuestro código y para refactorizarlo. Como tal, creo que es mi deber informarte sobre la lamentable injusticia que estás a punto de presenciar, indudablemente debido a mi limitado dominio del tema. Empecemos. ## Maldice Este Nido -Me gustaría abordar el tema del anidamiento. No el instintivo impulso que sienten quienes están a punto de ser padres cuando limpian y reordenan obsesiva e impulsivamente, sino del... bueno, ahora que lo pienso, eso no está tan lejos de la realidad, tal y como veremos en los próximos capítulos... En cualquier caso, lo que quiero decir con *anidamiento* es cuando se tienen dos o más tipos distintos todos acurrucados en torno a un valor, acunándolo, por así decirlo, como a un recién nacido. +Me gustaría abordar el tema del anidamiento. No el instintivo impulso que sienten quienes están a punto de ser padres cuando limpian y reordenan obsesiva e impulsivamente, sino del... bueno, ahora que lo pienso, eso no está tan lejos de la realidad, tal y como veremos en los próximos capítulos... En cualquier caso, lo que quiero decir con *anidamiento* es cuando se tienen dos o más tipos distintos, todos acurrucados en torno a un valor, acunándolo, por así decirlo, como a un recién nacido. ```js Right(Maybe('b')); @@ -16,7 +16,7 @@ IO(Task(IO(1000))); [Identity('bee thousand')]; ``` -Hasta ahora hemos logrado, mediante ejemplos cuidadosamente elaborados, evadirnos de este típico escenario, pero en la práctica, mientras programamos, los tipos tienden a enredarse entre ellos como el cable de unos auriculares en un exorcismo. Si no mantenemos a nuestros tipos meticulosamente bien organizados a medida que avanzamos, nuestro código se leerá más peludo que un hipster en un café de gatos. +Hasta ahora hemos logrado, mediante ejemplos cuidadosamente elaborados, evadirnos de tan típico escenario, pero en la práctica, mientras programamos, los tipos tienden a enredarse entre ellos como el cable de los auriculares en un exorcismo. Si no mantenemos a nuestros tipos meticulosamente bien organizados a medida que avanzamos, nuestro código se leerá más peludo que un hipster en un café de gatos. ## Una Comedia de Situación @@ -33,7 +33,7 @@ const saveComment = compose( ); ``` -La banda está aquí al completo, para consternación de nuestra firma de tipos. Permíteme explicar brevemente el código. Con `getValue('#comment')`, que es una acción que recupera el texto de un elemento, comenzamos obteniendo lo proporcionado por el usuario. Ahora bien, cabe la posibilidad de que se produzca un error al buscar el elemento o que el valor string no exista, así que devuelve `Task Error (Maybe String)`. Después de esto, debemos aplicar `map` tanto sobre `Task` como sobre `Maybe` para pasarle el texto a `validate`, quien a su vez nos entrega mediante `Either`, un `ValidationError` o nuestro `String`. A continuación, mapeamos durante días para enviar el `String` de nuestro `Task Error (Maybe (Either ValidationError String))` a `postComment`, que nos devuelve el `Task` resultante. +La pandilla está aquí al completo, para consternación de nuestra firma de tipos. Permíteme explicar brevemente el código. Con `getValue('#comment')`, que es una acción que recupera el texto de un elemento, comenzamos obteniendo lo proporcionado por el usuario. Ahora bien, cabe la posibilidad de que se produzca un error al buscar el elemento o que la cadena de texto no exista, así que devuelve `Task Error (Maybe String)`. Después de esto, debemos aplicar `map` tanto sobre `Task` como sobre `Maybe` para pasarle el texto a `validate`, quien a su vez nos entrega mediante `Either` un `ValidationError` o nuestro `String`. A continuación, mapeamos durante días para enviar el `String` de nuestro `Task Error (Maybe (Either ValidationError String))` a `postComment`, que nos devuelve el `Task` resultante. Qué desorden tan espantoso. Un collage de tipos abstractos, expresionismo de tipos amateur, un Pollock polimórfico, un Mondrian monolítico. Hay numerosas soluciones para este problema tan común. Podemos componer los tipos en un monstruoso contenedor, ordenarlos y aplicar `join` sobre algunos, homogeneizarlos, deconstruirlos, etc. En este capítulo nos centraremos en homogeneizarlos mediante *transformaciones naturales*. @@ -100,7 +100,7 @@ Además, se vuelve más fácil de optimizar / fusionar operaciones al mover `map ## JavaScript Isomórfico -Cuando podemos ir completamente hacia atrás y hacia adelante sin perder ninguna información, se considera un *isomorfismo*. Esta solo es una palabra elegante para decir que "mantiene los mismos datos". Decimos que dos tipos son *isomorfos* si podemos proporcionar las *transformaciones naturales* "hacia" y "desde" como demuestra: +Cuando podemos ir completamente hacia atrás y hacia adelante sin perder ninguna información, se considera que es un *isomorfismo*. Esta solo es una palabra elegante para decir que "mantiene los mismos datos". Decimos que dos tipos son *isomorfos* si podemos proporcionar las *transformaciones naturales* "hacia" y "desde" como demuestra: ```js // promiseToTask :: Promise a b -> Task a b @@ -158,7 +158,7 @@ Las leyes de la transformación natural también son válidas para estas funcion ## Una Solución Para El Anidamiento -Volviendo a nuestra cómica firma de tipos. Podemos espolvorear en ella algunas *transformaciones naturales* a lo largo del código que la llama para aplicar coerción de tipos a cada tipo que varíe y que así sean todos uniformes y que, por lo tanto, se puedan unir con `join`. +Volviendo a nuestra cómica firma de tipos. Para aplicar coerción de tipos a cada tipo que varíe podemos espolvorear en ella algunas *transformaciones naturales* a lo largo del código que la llama para que así sean todos uniformes y que, por lo tanto, se puedan unir con `join`. ```js // getValue :: Selector -> Task Error (Maybe String) @@ -181,7 +181,7 @@ const saveComment = compose( Las *Transformaciones Naturales* son funciones sobre nuestros funtores. Son un concepto extremadamente importante en la teoría de categorías y empezarán a aparecer por todas partes una vez que adoptemos más abstracciones, pero, por ahora, las hemos limitado a unas pocas aplicaciones concretas. Como hemos visto, podemos conseguir diferentes efectos al convertir tipos, con la garantía de que mantendremos nuestra composición. También pueden ayudarnos con el anidamiento de tipos, aunque tienen el efecto general de homogeneizar nuestros funtores al mínimo común denominador que, en la práctica, es el funtor con los efectos más volátiles (`Task` en la mayoría de los casos). -Esta continuada y tediosa clasificación de tipos es el precio que pagamos por haberlos materializado; convocados desde el éter. Por supuesto, los efectos implícitos son mucho más insidiosos, así que aquí estamos, librando esta justa batalla. Necesitaremos algunas herramientas más en nuestro equipamiento antes de poder enrollar las más largas amalgamas de tipos. A continuación, veremos cómo reordenar nuestros tipos con *Traversable*. +Este continuo y tedioso ordenamiento de tipos es el precio que pagamos por haberlos materializado; convocados desde el éter. Por supuesto, los efectos implícitos son mucho más insidiosos, así que aquí estamos, librando esta justa batalla. Necesitaremos algunas herramientas más en nuestro equipamiento antes de poder enrollar las más largas amalgamas de tipos. A continuación, veremos cómo reordenar nuestros tipos con *Traversable*. [Capítulo 12: Atravesando la Piedra](ch12-es.md) diff --git a/ch12-es.md b/ch12-es.md index fdaed27..69d418b 100644 --- a/ch12-es.md +++ b/ch12-es.md @@ -1,6 +1,6 @@ # Capítulo 12: Atravesando la Piedra -Hasta ahora, en nuestro circo de contenedores, nos has visto domar al feroz [funtor](ch08-es.md#mi-primer-funtor), doblegándolo a nuestra voluntad para realizar cualquier operación que se nos antoje. Has sido deslumbrado por los malabares hechos con multitud de peligrosos efectos simultáneamente utilizando [aplicación](ch10-es.md) de funciones para reunir los resultados. Presenciaste con asombro la desaparición de contenedores al ser [unidos](ch09-es.md) entre ellos. En el espectáculo de efectos secundarios, les hemos visto [componerse](ch08-es.md#un-poco-de-teoría) en uno solo. Y más recientemente, nos aventuramos más allá de lo natural y [transformamos](ch11-es.md) un tipo en otro ante tus propios ojos. +Hasta ahora, en nuestro circo de contenedores, nos has visto domar al feroz [funtor](ch08-es.md#mi-primer-funtor), doblegándolo a nuestra voluntad para realizar cualquier operación que se nos antojara. Te han deslumbrado los malabares hechos simultáneamente con múltiples efectos peligrosos, utilizando [aplicación](ch10-es.md) de funciones para reunir los resultados. Presenciaste con asombro como los contenedores se desvanecían en el aire al ser [unidos](ch09-es.md) entre ellos. En el espectáculo de efectos secundarios, los vimos [componerse](ch08-es.md#un-poco-de-teoría) en uno solo. Y más recientemente, nos aventuramos más allá de lo natural y [transformamos](ch11-es.md) un tipo en otro ante tus propios ojos. Y ahora, para nuestro siguiente truco, veremos los "traversables". Veremos tipos volar unos sobre otros como si fuesen trapecistas, manteniendo nuestro valor intacto. Reordenaremos los efectos como a las cabinas de una atracción de feria. Cuando nuestros contenedores se entrelacen como las extremidades de un contorsionista podremos utilizar esta interfaz para enderezar las cosas. Con distintas disposiciones presenciaremos distintos efectos. Tráeme mis bombachos y mi flauta de émbolo, comencemos. @@ -33,7 +33,7 @@ He aquí un último ejemplo de una situación complicada: const getControlNode = compose(map(map($)), map(getAttribute('aria-controls')), $); ``` -Mira esos `IO` anhelando estar juntos. Sería simplemente encantador poder unirlos con `join` y dejar que bailasen mejilla con mejilla, pero, por desgracia, un `Maybe` se interpone entre ellos como una carabina en el baile de graduación. Nuestro mejor movimiento aquí sería colocarlos uno junto al otro para que sus tipos estuviesen al fin juntos, y así simplificar nuestra firma a `IO (Maybe Node)`. +Mira esos `IO` anhelando estar juntos. Sería simplemente encantador poderlos unir con `join` permitiéndoles bailar mejilla con mejilla, pero, por desgracia, un `Maybe` se interpone entre ellos como una carabina en el baile de graduación. Nuestro mejor movimiento aquí sería colocarlos uno junto al otro para que sus tipos estuviesen al fin juntos, y así simplificar nuestra firma a `IO (Maybe Node)`. ## Feng Shui de Tipos @@ -56,9 +56,9 @@ sequence(Task.of, left('wing')); // Task(Left('wing')) const sequence = curry((of, x) => x.sequence(of)); ``` -Comencemos por el segundo argumento. Ha de ser un *Traversable* conteniendo un *Aplicativo* que, aun sonando bastante restrictivo, suele ser lo más común. Es el `t (f a)` quien es transformado en `f (t a)`. ¿No es expresivo? Queda claro como el agua que los dos tipos bailan dos-à-dos el uno alrededor del otro. El primer argumento es tan solo una muleta y tan solo es necesario en un lenguaje sin tipos. Es un constructor de tipo (nuestro *of*) proporcionado para que podamos invertir tipos como `Left`, reacios a `map`; más sobre esto en un minuto. +Comencemos por el segundo argumento. Ha de ser un *Traversable* conteniendo un *Aplicativo* que, aun sonando bastante restrictivo, suele ser lo más común. Es el `t (f a)` quien es transformado en `f (t a)`. ¿No es expresivo? Queda claro como el agua que los dos tipos bailan dos-à-dos el uno alrededor del otro. El primer argumento es tan solo una ayuda y solo es necesario en un lenguaje sin tipos. Es un constructor de tipo (nuestro *of*) proporcionado para que podamos invertir tipos reacios a `map`, como `Left`; más sobre esto en un minuto. -Utilizando `sequence` podemos mover tipos de un lado a otro con la precisión de un trilero. Pero ¿cómo funciona esto? Veamos como un tipo, por ejemplo `Either`, la implementaría. +Utilizando `sequence` podemos mover tipos de un lado a otro con la precisión de un trilero. Pero ¿cómo funciona esto? Veamos como un tipo, digamos `Either`, la implementaría. ```js class Right extends Either { @@ -82,7 +82,7 @@ class Left extends Either { } ``` -Nos gustaría que los tipos acabasen siempre en la misma disposición, por lo que es necesario que tipos como `Left`, que no contienen a nuestro aplicativo interno, reciban algo de ayuda para hacerlo. La interfaz *Aplicativo* requiere que primero tengamos un *Funtor Pointed* para que siempre tengamos un *of* que pasar. En un lenguaje con sistema de tipos, el tipo externo puede ser inferido de la firma y no necesita ser proporcionado explícitamente. +Queremos que los tipos acaben siempre en la misma disposición, por lo que es necesario que tipos como `Left`, que no contienen a nuestro aplicativo interno, reciban algo de ayuda para hacerlo. La interfaz *Aplicativo* requiere que primero tengamos un *Funtor Pointed* para que siempre tengamos un *of* que pasar. En un lenguaje con sistema de tipos, el tipo externo puede ser inferido de la firma y no necesita ser proporcionado explícitamente. ## Surtido de Efectos @@ -134,7 +134,7 @@ Esto tan solo ejecuta un `reduce` en la lista. La función reduce es `(f, a) => 5. .`ap(f)` - Recuerda que aquí `f` es un Aplicativo, así que podemos aplicar la función `bs => bs.concat(b)` a cualquier valor `bs :: [a]` que esté en `f`. Afortunadamente para nosotros, `f` proviene de nuestra semilla inicial y tiene el siguiente tipo: `f :: Either e [a]` que, por cierto, se conserva cuando aplicamos `bs => bs.concat(b)`. + Recuerda que aquí `f` es un Aplicativo, así que podemos aplicar la función `bs => bs.concat(b)` a cualquier valor `bs :: [a]` que esté en `f`. Afortunadamente, `f` proviene de nuestra semilla inicial y tiene el siguiente tipo: `f :: Either e [a]` que, por cierto, se conserva cuando aplicamos `bs => bs.concat(b)`. Cuando `f` es `Right` llama a `bs => bs.concat(b)`, quien a su vez devuelve un `Right` con el elemento añadido a la lista. Cuando es `Left`, el valor izquierdo (del paso anterior o de la iteración anterior respectivamente) es devuelto. > fn(a).map(b => bs => bs.concat(b)).ap(f) :: Either e [a] @@ -174,7 +174,7 @@ En vez de `map(map($))` tenemos `chain(traverse(IO.of, $))`, que invierte nuestr ## Sin Ley Ni Orden -Bien, ahora, antes de que te pongas a juzgar y golpees la tecla de borrar como con un mazo para olvidar el capítulo, tómate un momento para reconocer que todas estas leyes son útiles garantías de código. Es conjetura mía que la finalidad de las arquitecturas de muchos programas es intentar poner restricciones útiles en nuestro código para reducir las posibilidades, para guiarnos hacia las respuestas cuando lo diseñamos y cuando lo leemos. +Bien, ahora, antes de que te pongas a juzgar y golpees la tecla de borrar como con un mazo para olvidar el capítulo, tómate un momento para reconocer que todas estas leyes son útiles garantías de código. Es conjetura mía que la finalidad de las arquitecturas de muchos programas es intentar poner restricciones útiles en nuestro código para así reducir las posibilidades, para guiarnos hacia las respuestas cuando lo diseñamos y cuando lo leemos. Una interfaz sin leyes es simple indirección. Como cualquier otra estructura matemática, debemos exponer las propiedades para nuestra propia cordura. Esto tiene un efecto similar a la encapsulación, dado que protege a los datos, permitiéndonos intercambiar la interfaz por otro ciudadano ejemplar. @@ -194,7 +194,7 @@ identity2(Either.of('stuff')); // Identity(Right('stuff')) ``` -Esto debería ser sencillo. Si colocamos un `Identity` dentro de nuestro funtor, y luego le damos la vuelta con `sequence` es lo mismo que colocarlo por fuera desde el principio. Hemos elegido a `Right` como conejillo de indias porque con él es fácil probar a aplicar la ley e inspeccionarlo. Podríamos haber usado cualquier otro funtor, sin embargo, el usar un funtor concreto como `Identity` en la propia ley, podría haber levantado algunas cejas. Recuerda que una [categoría](ch05-es.md#teoría-de-categorías) es definida por morfismos entre sus objetos con composición asociativa e identidad. Cuando se trata de la categoría de funtores, las transformaciones naturales son los morfismos e `Identity` es, bueno, la identidad. El funtor `Identity` es tan fundamental para demostrar las leyes como nuestra función `compose`. De hecho, deberíamos dejar aquí este tema y pasar a hacer lo mismo con nuestro tipo [Compose](ch08-es.md#un-poco-de-teoría): +Esto debería ser sencillo. Si colocamos un `Identity` dentro de nuestro funtor, y luego le damos la vuelta con `sequence` es lo mismo que colocarlo por fuera desde el principio. Hemos elegido a `Right` como conejillo de indias porque con él es fácil probar a aplicar la ley e inspeccionarlo. Podríamos haber usado cualquier otro funtor, sin embargo, el uso de un funtor concreto como `Identity` en la ley misma, puede que haya levantado algunas cejas. Recuerda que una [categoría](ch05-es.md#teoría-de-categorías) se define como morfismos entre sus objetos con composición asociativa e identidad. Cuando se trata de la categoría de funtores, las transformaciones naturales son los morfismos e `Identity` es, bueno, la identidad. El funtor `Identity` es tan fundamental para demostrar las leyes como nuestra función `compose`. De hecho, deberíamos dejar aquí este tema y pasar a hacer lo mismo con nuestro tipo [Compose](ch08-es.md#un-poco-de-teoría): ### Composición @@ -211,7 +211,7 @@ comp2(Either.of, Array)(Identity(Right([true]))); // Compose(Right([Identity(true)])) ``` -Esta ley preserva la composición tal y como se esperaba: si intercambiamos la composición de funtores, no deberíamos tener ninguna sorpresa dado que la composición es un funtor en sí mismo. Arbitrariamente hemos escogido `true`, `Right`, `Identity` y `Array` para probarlo. Librerías como [quickcheck](https://hackage.haskell.org/package/QuickCheck) o [jsverify](http://jsverify.github.io/) pueden ayudarnos a comprobar la ley mediante pruebas con datos aleatorios en las entradas. +Tal y como podríamos esperar, esta ley preserva la composición: si intercambiamos la composición de funtores, no deberíamos tener ninguna sorpresa dado que la composición es un funtor en sí mismo. Arbitrariamente hemos escogido `true`, `Right`, `Identity` y `Array` para probarlo. Librerías como [quickcheck](https://hackage.haskell.org/package/QuickCheck) o [jsverify](http://jsverify.github.io/) pueden ayudarnos a comprobar la ley mediante pruebas con datos aleatorios en las entradas. Como consecuencia natural de la ley de arriba, obtenemos la capacidad de [fusionar *traversals*](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf), lo que es bueno desde el punto de vista del rendimiento. @@ -221,7 +221,7 @@ Como consecuencia natural de la ley de arriba, obtenemos la capacidad de [fusion const natLaw1 = (of, nt) => compose(nt, sequence(of)); const natLaw2 = (of, nt) => compose(sequence(of), map(nt)); -// comprobar con una transformación natural al azar y nuestros amigables funtores Identity/Right. +// test con una transformación natural al azar y nuestros amigables funtores Identity/Right. // maybeToEither :: Maybe a -> Either () a const maybeToEither = x => (x.$value ? new Right(x.$value) : new Left()); @@ -233,7 +233,7 @@ natLaw2(Either.of, maybeToEither)(Identity.of(Maybe.of('barlow one'))); // Right(Identity('barlow one')) ``` -Esto es parecido a nuestra ley de la identidad. Si primero meneamos a los tipos y luego ejecutamos una transformación natural en el exterior, debería ser lo mismo que mapear una transformación natural y después voltear los tipos. +Esto es parecido a nuestra ley de la identidad. Si primero hacemos girar a los tipos y luego ejecutamos una transformación natural en el exterior, debería ser lo mismo que mapear una transformación natural y después voltear los tipos. Una consecuencia natural de esta ley es: @@ -290,7 +290,7 @@ const validate = player => (player.name ? Either.of(player) : left('must have na {% exercise %} -Usando traversable, y la función `validate`, actualiza `startGame` (y su firma) +Usando traversable y la función `validate`, actualiza `startGame` (y su firma) para que solo comience el juego si todos los jugadores son válidos diff --git a/ch13-es.md b/ch13-es.md index 184dca5..8803a94 100644 --- a/ch13-es.md +++ b/ch13-es.md @@ -2,9 +2,9 @@ ## Salvaje Combinación -En este capítulo, examinaremos a los *monoides* mediante *semigrupos*. Los *monoides* son el chicle en el pelo de la abstracción matemática. Capturan una idea que comprende múltiples disciplinas y, figurativa y literalmente, las une a todas en una. Son la fuerza ominosa que conecta todo aquello que tiene la capacidad de calcular. Son el oxígeno en nuestra base de código, el suelo en el que se ejecuta, entrelazamiento cuántico codificado. +En este capítulo examinaremos los *monoides* utilizando *semigrupos*. Los *monoides* son el chicle en el pelo de la abstracción matemática. Capturan una idea que abarca múltiples disciplinas, uniéndolas figurativa y literalmente todas en una. Son la fuerza ominosa que conecta todo aquello que tiene la capacidad de calcular. Son el oxígeno en nuestra base de código, la tierra sobre la que se ejecuta, entrelazamiento cuántico codificado. -Los *monoides* tratan sobre combinar. Pero, ¿qué es combinar? Puede significar muchas cosas, desde acumulación hasta concatenación pasando por multiplicación o elección, composición, ordenación, ¡incluso evaluación! Veremos numerosos ejemplos, pero tan solo rozaremos la falda de la montaña de los monoides. Los ejemplares son abundantes y las aplicaciones, enormemente amplias. El objetivo de este capítulo es proporcionarte una buena intuición para que puedas crear tus propios *monoides*. +Los *monoides* tratan sobre combinar. Pero, ¿qué es combinar? Puede significar muchas cosas, desde acumulación hasta concatenación pasando por multiplicación o elección, composición, ordenación, ¡incluso evaluación! Veremos numerosos ejemplos, pero tan solo pasaremos de soslayo por la falda de la montaña de los monoides. Los ejemplares son abundantes y las aplicaciones, enormemente amplias. El objetivo de este capítulo es proporcionarte una buena intuición para que puedas crear tus propios *monoides*. ## Abstrayendo La Suma @@ -34,9 +34,9 @@ Además, tenemos la asociatividad, que nos da la capacidad de agrupar operacione No vayas a confundir esto con conmutatividad, la cual nos permite cambiar el orden. Aunque se mantiene para la suma, ahora mismo no estamos especialmente interesados en esta propiedad; demasiado específica para nuestras necesidades de abstracción. -Ahora que lo pienso, ¿qué propiedades deben estar si o si en nuestra superclase abstracta? ¿Qué rasgos son específicos de la suma y cuáles pueden ser generalizados? ¿Hay otras abstracciones en medio de esta jerarquía o es todo un mismo trozo? Este es el tipo de razonamiento que nuestros antepasados matemáticos aplicaban cuando concebían las interfaces en el álgebra abstracta. +Ahora que lo pienso, ¿qué propiedades deben estar si o si en nuestra superclase abstracta? ¿Qué rasgos son específicos de la suma y cuáles pueden ser generalizados? ¿Hay otras abstracciones en medio de esta jerarquía o es todo un mismo trozo? Este es el tipo de razonamiento que nuestros antepasados matemáticos aplicaban cuando concibieron las interfaces en el álgebra abstracta. -Como era de esperar, cuando estos "abstraccionistas" de la vieja escuela abstrajeron la suma llegaron al concepto de *grupo*. Un *grupo* tiene todo lo que se necesita incluyendo el concepto de números negativos. Ahora mismo solo estamos interesados en el operador binario asociativo así que elegiremos una interfaz menos específica, *Semigrupo*. Un *Semigrupo* es un tipo con un método `concat` que hace de operador binario asociativo. +Como era de esperar, cuando estos "abstraccionistas" de la vieja escuela abstrajeron la suma llegaron al concepto de *grupo*. Un *grupo* tiene de todo, incluyendo el concepto de números negativos. Ahora mismo solo estamos interesados en el operador binario asociativo así que elegiremos una interfaz menos específica, *Semigrupo*. Un *Semigrupo* es un tipo con un método `concat` que hace de operador binario asociativo. Implementémoslo para la suma y llamémosle `Sum`: @@ -60,7 +60,7 @@ Así podemos programar para la interfaz, no para la implementación. Dado que es Como mencionaba antes, `Sum` no es *pointed*, y tampoco es un *funtor*. Como ejercicio, vuelve atrás y comprueba las leyes para ver por qué. Vale, yo te lo diré: únicamente puede mantener un número, así que `map` no tiene sentido aquí dado que no podemos transformar al valor subyacente en otro tipo. ¡Ese sería un `map` muy limitado de hecho! -Y entonces, ¿por qué es útil? Bien, como con cualquier interfaz, podemos cambiar nuestro ejemplar para conseguir distintos resultados: +Y entonces, ¿por qué es útil? Bien, como con cualquier interfaz, podemos cambiar nuestra implementación para conseguir distintos resultados: ```js const Product = x => ({ x, concat: other => Product(x * other.x) }) @@ -107,17 +107,17 @@ Identity.of(Sum(4)).concat(Identity.of(Sum(1))) // Identity(Sum(5)) Identity.of(4).concat(Identity.of(1)) // TypeError: this.__value.concat is not a function ``` -Es un *semigrupo* si y solo si su valor `__value` es un *semigrupo*. Como alguien torpe pilotando un ala delta, solo lo es mientras está agarrado a uno. +Es un *semigrupo* si y solo si su valor `__value` es un *semigrupo*. Como un torpe piloto de ala delta, solo lo es mientras está agarrado a una. Otros tipos tienen un comportamiento similar: ```js -// combinación con manejo de errores +// combinar con manejo de errores Right(Sum(2)).concat(Right(Sum(3))) // Right(Sum(5)) Right(Sum(2)).concat(Left('some error')) // Left('some error') -// combina asincronía +// combinar asincronía Task.of([1,2]).concat(Task.of([3,4])) // Task([1,2,3,4]) ``` @@ -136,7 +136,7 @@ serverA.get('/friends').concat(serverB.get('/friends')) // Task([friend1, friend loadSetting('email').concat(loadSetting('general')) // Task(Maybe(Map({backgroundColor: true, autoSave: false}))) ``` -En el ejemplo de arriba, hemos combinado un `IO` que contiene un `Either` que a su vez contiene un `Map` para validar y fusionar los valores del formulario. Después hemos llamado a un par de servidores distintos y hemos combinado sus resultados de manera asíncrona utilizando `Task` y `Array`. Finalmente hemos apilado `Task`, `Maybe` y `Map` para cargar, parsear y fusionar múltiples ajustes. +En el ejemplo de arriba, para validar y fusionar los valores del formulario hemos combinado un `IO` que contiene un `Either` que a su vez contiene un `Map`. Después hemos llamado a un par de servidores distintos y hemos combinado sus resultados de manera asíncrona utilizando `Task` y `Array`. Finalmente hemos apilado `Task`, `Maybe` y `Map` para cargar, parsear y fusionar múltiples ajustes. Estos ejemplos podrían haber utilizado `chain` o `ap`, pero los *semigrupos* capturan lo que queremos de forma mucho más concisa. @@ -164,7 +164,7 @@ Map({clicks: Sum(2), path: ['/home', '/about'], idleTime: Right(Max(2000))}).con Podemos apilar y combinar tantos como queramos. Solo es cuestión de añadir otro árbol al bosque, u otra llama al incendio del bosque dependiendo de tu base de código. -El comportamiento intuitivo por defecto es combinar lo que el tipo contiene, sin embargo, hay casos en los que ignoramos lo que hay dentro y combinamos el contenedor en sí mismo. Considera un tipo como `Stream` (Flujo): +El comportamiento intuitivo por defecto es combinar lo que el tipo contiene, sin embargo, hay casos en los que ignoramos lo que hay dentro y combinamos el contenedor en sí mismo. Considera un tipo como `Stream` [*Flujo*]: ```js const submitStream = Stream.fromEvent('click', $('#submit')) @@ -179,7 +179,7 @@ Podemos combinar flujos de eventos [*event streams*] capturando los eventos de a Estamos abstrayendo la suma pero, como a los babilonios, nos falta el concepto de cero (hubo cero menciones sobre él). -El cero actúa como la *identidad* queriendo decir que cualquier elemento añadido a `0` devolverá ese mismo elemento. En términos de abstracción, sirve de ayuda pensar en el `0` como en un elemento neutral o *vacío*. Es importante el hecho de que actúa de la misma manera tanto en el lado izquierdo como en el derecho de nuestra operación binaria: +El cero actúa como la *identidad* queriendo decir que cualquier elemento añadido a `0` devolverá ese mismo elemento. En términos de abstracción, sirve de ayuda pensar en el `0` como en un elemento neutro o *vacío*. Es importante el hecho de que actúa de la misma manera tanto en el lado izquierdo como en el lado derecho de nuestra operación binaria: ```js // identidad @@ -220,7 +220,7 @@ sum([]) // 0 También son el valor inicial perfecto para un acumulador... -## Doblando La Casa +## Plegando La Casa Resulta que `concat` y `empty` encajan perfectamente con los dos primeros huecos de `reduce`. De hecho podemos aplicar `reduce` a un array de *semigrupos* ignorando el valor *vacío*, pero, como puedes ver, esto conduce a una precaria situación: @@ -233,7 +233,7 @@ const concat = x => y => x.concat(y) [].reduce(concat) // TypeError: Reduce of empty array with no initial value ``` -Y la dinamita explota. Como un tobillo torcido en una maratón, obtenemos un error de ejecución. JavaScript es más que feliz dejando que nos atemos pistolas a nuestras zapatillas deportivas antes de salir a correr; es algo así como un lenguaje conservador, supongo, que nos detiene en seco cuando el array se vuelve infértil. ¿Qué podría devolver si no?¿`Nan`, `false`, `-1`? Si fuésemos a continuar con nuestro programa, querríamos un resultado del tipo correcto. Podría devolver un `Maybe` para indicar la posibilidad de fallo, pero podemos hacer algo mejor. +Y la dinamita explota. Como un tobillo torcido en una maratón, obtenemos un error de ejecución. JavaScript es más que feliz dejando que nos atemos pistolas a nuestras zapatillas deportivas antes de salir a correr; es algo así como un lenguaje conservador, supongo, pero que nos detiene en seco cuando el array es estéril. ¿Qué podría devolver si no?¿`Nan`, `false`, `-1`? Si fuésemos a continuar con nuestro programa, querríamos un resultado del tipo correcto. Podría devolver un `Maybe` para indicar la posibilidad de fallo, pero podemos hacer algo mejor. Vamos a utilizar nuestra versión currificada de `reduce` y a hacer una versión segura donde el valor vacío no sea opcional. En lo sucesivo será conocida como `fold`: @@ -258,7 +258,7 @@ fold(Either.of(Max.empty()), [Right(Max(3)), Left('error retrieving value'), Rig fold(IO.of([]), ['.link', 'a'].map($)) // IO([,