Capítulo 10: Funtores Aplicativos
Aplicando Funtores
El nombre funtor aplicativo resulta descriptivo dado sus orígenes funcionales. Los programadores funcionales son notorios por usar términos como mappend o liftA4, que parecen naturales en laboratorios matemáticos, pero tienen la claridad de un Darth Vader indeciso en un drive-thru fuera de contexto.
En cualquier caso, el nombre revela su propósito principal: la capacidad de aplicar funtores entre sí.
¿Por qué una persona racional como tú querría esto? ¿Qué significa realmente aplicar un funtor a otro?
Para responder, partamos de un escenario común: supongamos dos funtores (mismo tipo) donde necesitamos pasar ambos valores como argumentos a una función. Por ejemplo, sumar valores de dos Container.
// We can't do this because the numbers are bottled up.
add(Container.of(2), Container.of(3));
// NaN
// Let's use our trusty map
const containerOfAdd2 = map(add, Container.of(2));
// Container(add(2))Tenemos un Container con una función parcialmente aplicada. Concretamente: Container(add(2)) aplicará add(2) al valor 3 en Container(3). Es decir, aplicar un funtor a otro.
Podemos lograr esto usando chain y luego mapeando add(2) así:
Container.of(2).chain(two => Container.of(3).map(add(two)));El problema es quedar atrapado en la secuencialidad monádica donde nada se evalúa hasta que termine la mónada anterior. Tenemos valores independientes donde retrasar Container(3) es innecesario.
Sería ideal aplicar contenidos de funtores sin funciones/variables superfluas cuando sea posible.
Barcos en Botellas

ap es una función que aplica el contenido funcional de un funtor al valor de otro. Dígalo cinco veces rápido.
Container.of(add(2)).ap(Container.of(3));
// Container(5)
// all together now
Container.of(2).map(add).ap(Container.of(3));
// Container(5)Aquí está: limpio y ordenado. Container(3) se libera del anidamiento monádico. Recordemos que add está parcialmente aplicado (requiere currificación).
Definimos ap así:
Container.prototype.ap = function (otherContainer) {
return otherContainer.map(this.$value);
};Dado que this.$value es una función, mapeamos otro funtor. Así definimos la interfaz:
Un funtor aplicativo es un funtor pointed con método
ap
Nota la dependencia de pointed. Esta característica será clave en los ejemplos.
Mantén la mente abierta: ap demostrará su utilidad. Primero exploremos una propiedad clave.
F.of(x).map(f) === F.of(f).ap(F.of(x));Mapear f equivale a aplicar ap con un funtor de f. O bien: insertar x y mapear f, o elevar ambos y aplicar. Esto permite código secuencial:
Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
// Maybe(5)
Task.of(add).ap(Task.of(2)).ap(Task.of(3));
// Task(5)Entrecerrando los ojos verás una llamada normal. Usando of, los valores entran al universo paralelo de contenedores donde ap aplica funciones asíncronamente. Como construir un barco en miniatura.
Nota el uso de Task: aquí brillan los funtores aplicativos. Veamos otro caso:
Coordinación de Motivación
Imagina un sitio de viajes que consulta destinos turísticos y eventos locales mediante APIs separadas.
// Http.get :: String -> Task Error HTML
const renderPage = curry((destinations, events) => { /* render page */ });
Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'));
// Task("<div>some page with dest and events</div>")Ambas llamadas HTTP se resuelven en paralelo, y renderPage ejecuta tras finalizar. Las mónadas secuencian Tasks individualmente. Aquí no necesitamos secuencialidad.
Al usar currificación, renderPage debe estar currificada para esperar ambos Tasks. Este enfoque simplifica tareas complejas manuales.
Otro ejemplo:
// $ :: String -> IO DOM
const $ = selector => new IO(() => document.querySelector(selector));
// getVal :: String -> IO String
const getVal = compose(map(prop('value')), $);
// signIn :: String -> String -> Bool -> User
const signIn = curry((username, password, rememberMe) => { /* signing in */ });
IO.of(signIn).ap(getVal('#email')).ap(getVal('#password')).ap(IO.of(false));
// IO({ id: 3, email: 'gg@allin.com' })signIn es una función currificada de 3 argumentos. Cada ap añade un argumento. Nota: los últimos argumentos requieren of para elevación de tipos.
Hermano, ¿Realmente Levantas?
Forma pointfree: sabiendo que map equivale a of/ap, creamos funciones que aplican múltiples veces:
const liftA2 = curry((g, f1, f2) => f1.map(g).ap(f2));
const liftA3 = curry((g, f1, f2, f3) => f1.map(g).ap(f2).ap(f3));
// liftA4, etcliftA2 suena a ascensor industrial. Su propósito es claro: elevar elementos al mundo aplicativo.
Los sufijos numéricos parecen redundantes, pero permiten aplicar parcialmente liftA(N).
Ejemplo práctico:
// checkEmail :: User -> Either String Email
// checkName :: User -> Either String String
const user = {
name: 'John Doe',
email: 'blurp_blurp',
};
// createUser :: Email -> String -> IO User
const createUser = curry((email, name) => { /* creating... */ });
Either.of(createUser).ap(checkEmail(user)).ap(checkName(user));
// Left('invalid email')
liftA2(createUser, checkEmail(user), checkName(user));
// Left('invalid email')Usando liftA2 para createUser, omitimos referencias a Either, ganando flexibilidad tipográfica.
Reescribiendo ejemplos previos:
liftA2(add, Maybe.of(2), Maybe.of(3));
// Maybe(5)
liftA2(renderPage, Http.get('/destinations'), Http.get('/events'));
// Task('<div>some page with dest and events</div>')
liftA3(signIn, getVal('#email'), getVal('#password'), IO.of(false));
// IO({ id: 3, email: 'gg@allin.com' })Operadores
En lenguajes como Haskell, verás sintaxis como:
-- Haskell / PureScript
add <$> Right 2 <*> Right 3// JavaScript
map(add, Right(2)).ap(Right(3));Recordatorio: <$> es map (alias fmap) y <*> es ap. Mejoran legibilidad reduciendo paréntesis.
Abridores Gratuitos

Las funciones derivadas existen. Al obedecer leyes matemáticas, interfaces simples componen otras complejas.
Un aplicativo es primero un funtor. Una instancia aplicativa permite definir su funtor asociado:
Esta armonía proviene del marco matemático. Ni Mozart mejoraría esta composición.
Como of/ap equivale a map, podemos definir map gratuitamente:
// map derived from of/ap
X.prototype.map = function map(f) {
return this.constructor.of(f).ap(this);
};Las mónadas están arriba en la jerarquía. Con chain, obtenemos funtor/aplicativo gratis:
// map derived from chain
X.prototype.map = function map(f) {
return this.chain(a => this.constructor.of(f(a)));
};
// ap derived from chain/map
X.prototype.ap = function ap(other) {
return this.chain(f => other.map(f));
};Definiendo una mónada obtenemos sus subclases. Notablemente, podemos automatizar este proceso.
ap permite ejecución concurrente. Definirlo mediante chain pierde esta optimización, pero ofrece funcionalidad básica.
¿Por qué no usar solo mónadas? Usa el nivel de abstracción mínimo necesario. Los aplicativos simplifican casos no secuenciales.
Las mónadas secuencian cómputos y asignan variables. Los aplicativos operan en paralelo sin estas complejidades.
Ahora, hablemos de legalidades...
Leyes
Los funtores aplicativos conservan tipos bajo composición (por ello se prefieren sobre mónadas). Permiten combinar efectos manteniendo coherencia tipográfica.
Ejemplo:
const tOfM = compose(Task.of, Maybe.of);
liftA2(liftA2(concat), tOfM('Rainy Days and Mondays'), tOfM(' always get me down'));
// Task(Maybe(Rainy Days and Mondays always get me down))Sin conflictos de tipos durante la composición.
Ley fundamental de identidad:
Identidad
// identity
A.of(id).ap(v) === v;Aplicar id desde un funtor no altera su valor:
const v = Identity.of('Pillow Pets');
Identity.of(id).ap(v) === v;Identity.of(id) parece redundante. Como of/ap equivale a map, esta ley deriva de map(id) == id.
Estas leyes actúan como árbitros, asegurando compatibilidad entre interfaces.
Homomorfismo
// homomorphism
A.of(f).ap(A.of(x)) === A.of(f(x));Un homomorfismo preserva estructura. Los funtores son homomorfismos entre categorías.
Aplicar dentro del contenedor vs externamente produce el mismo resultado, preservando estructura.
Ejemplo simple:
Either.of(toUpperCase).ap(Either.of('oreos')) === Either.of(toUpperCase('oreos'));Intercambio
La ley de intercambio permite elegir el lado para elevar la función en ap:
// interchange
v.ap(A.of(x)) === A.of(f => f(x)).ap(v);Ejemplo:
const v = Task.of(reverse);
const x = 'Sparklehorse';
v.ap(Task.of(x)) === Task.of(f => f(x)).ap(v);Composición
Garantiza que la composición estándar funcione dentro de contenedores:
// composition
A.of(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w));const u = IO.of(toUpperCase);
const v = IO.of(concat('& beyond'));
const w = IO.of('blood bath ');
IO.of(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w));Resumen
Casos ideales: múltiples argumentos en funtores. Prefiere aplicativos cuando no se requiere secuenciamiento monádico.
Casi terminamos con APIs contenedoras. Próximo capítulo: manipulación avanzada de múltiples funtores.
Capítulo 11: Transformando de Nuevo, Naturalmente
Ejercicios
¡Practiquemos!
Escribe una función que sume dos números posiblemente nulos usando Maybe y ap.
// safeAdd :: Maybe Number -> Maybe Number -> Maybe Number
const safeAdd = undefined;
¡Practiquemos!
Reescribe safeAdd del ejercicio_b usando liftA2 en lugar de ap.
// safeAdd :: Maybe Number -> Maybe Number -> Maybe Number
const safeAdd = undefined;
Para el siguiente ejercicio, considera estas funciones:
const localStorage = {
player1: { id:1, name: 'Albert' },
player2: { id:2, name: 'Theresa' },
};
// getFromCache :: String -> IO User
const getFromCache = x => new IO(() => localStorage[x]);
// game :: User -> User -> String
const game = curry((p1, p2) => `${p1.name} vs ${p2.name}`);¡Practiquemos!
Crea un IO que obtenga player1 y player2 de la caché e inicie el juego. // startGame :: IO String const startGame = undefined;