slugo / jerga-programacion-funcional

Jerga de la programación funcional en términos fáciles de entender!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Jerga de la Programación Funcional

La programación funcional ofrece muchas ventajas, de ahí el auge de su popularidad. Sin embargo, cada paradigma de programación trae consigo una jerga única, y la programación funcional no es la excepción. Al proveer un glosario, creemos que podemos ayudar a aprender programación funcional de una manera más fácil.

Los ejemplos presentados están escritos en JavaScript (ES2015). Por qué JavaScript?

Este es un trabajo en progreso; siéntase libre en enviar PR.

Traducciones

Tabla de Contenido

Aridad

El número de argumentos que acepta una función. Derivada de unaria, binaria, ternaria, etc… Esta palabra tiene la distinción de estar compuesta de dos sufijos, “-aria” y “-idad”. Por ejemplo, la suma, acepta dos argumentos, y por lo tanto se define como una función binaria o una función de aridad dos. De igual manera, una función que acepta un número variable de argumentos es llamada de aridad variable.

const suma = (a, b) => a + b

const aridad = suma.length
console.log(aridad) // 2

// La aridad de la suma es 2

Funciones de Orden Superior (FOS)

Función que acepta una función como argumento y/o retorna otra función.

const filtro = (predicado, xs) => {
  const resultado = []
  for (let idx = 0; idx < xs.length; idx++) {
    if (predicado(xs[idx])) {
      resultado.push(xs[idx])
    }
  }
  return resultado
}
const es = (tipo) => (x) => Object(x) instanceof tipo
filtro(es(Number), [0, '1', 2, null]) // [0, 2]

Aplicación Parcial

Aplicar parcialmente una función significa crear una nueva función al llamar la función original fijando algunos de sus argumentos.

// Helper para crear funciones aplicadas parcialmente
// Acepta una función y algunos argumentos
const parcial = (f, ...args) =>
  // retorna una función que acepta el resto de los argumentos
  (...masArgs) =>
    // y llama la función original con todos ellos
    f(...args, ...masArgs)

// Algo para aplicar
const suma3 = (a, b, c) => a + b + c

// Aplicar parcialmente `2` y `3` a `sumar3` retorna una función que acepta un argumento
const cincoMas = parcial(suma3, 2, 3) // (c) => 2 + 3 + c

cincoMas(4) // 9

También podemos utilizar Function.prototype.bind para aplicar parcialmente una función en JS:

const suma1mas = suma3.bind(null, 2, 3) // (c) => 2 + 3 + c

La aplicación parcial permite crear funciones simples a partir de funciones complejas a medida que uno va obteniendo datos. La funciones “currying” son parcialmente aplicada de manera automática.

Currying

Es el proceso de convertir una función que acepta múltiples argumentos a una función que acepta dichos argumentos uno a la vez.

Cada vez que la función es invocada solo acepta un solo argumento y retorna otra función que a su vez acepta un solo argumento hasta que todos los argumentos hayan sido pasados.

const suma = (a, b) => a + b

const sumaCurried = (a) => (b) => a + b

sumaCurried(40)(2) // 42.

const suma2 = sumaCurried(2) // (b) => 2 + b

suma2(10) // 12

Auto Currying

Transformar una función que acepta múltiples argumentos en una que al recibir menos argumentos que el número total que acepta, retorna una función que acepta el resto de los argumentos. Cuando la función recibe el número correcto de argumentos, entonces es evaluada.

Underscore, lodash, y ramda tienen una función curry que funciona de esta manera.

const suma = (x, y) => x + y

const sumaCurried = _.curry(suma)
sumaCurried(1, 2) // 3
sumaCurried(1) // (y) => 1 + y
sumaCurried(1)(2) // 3

Lecturas Adicionales (en inglés)

Composición de Funciones

El acto de unir dos funciones para formar una tercera función donde la salida de una función es la entrada de la otra.

const componer = (f, g) => (a) => f(g(a)) // Definición
const redondearYtoString = componer((val) => val.toString(), Math.floor) // Uso
redondearYtoString(121.212121) // '121'

Pureza

Una función es pura si el valor de retorno está solamente determinado por sus valores de entrada, y no produce efectos secundarios.

const saludar = (nombre) => 'Hola ' + nombre

saludar('Amelie') // 'Hola Amelie'

Contrario a:

let saludo

const saludar = () => {
  saludo = 'Hola, ' + window.name
}

saludar() // "Hola Amelie"

Efectos secundarios

Se dice que una función o expresión tiene efectos secundarios si aparte de retornar un valor, esta interactúa (lee o escribe) con algún estado mutable externo.

const diferenteCadaVez = new Date()
console.log('E/S (entrada y salida) es un efecto secundario!')

Idempotente

Una función es idempotente cuando al ser invocada con su resultado no produce un resultado diferente.

f(f(x)) ≍ f(x)
Math.abs(Math.abs(10))
sort(sort(sort([2, 1])))

Estilo Point-Free

Escribir funciones donde su definición no identifica explícitamente los argumentos usados. Este estilo usualmente requiere currying u otras Funciones de Orden Superior (FOS). También conocido como Programación Tácita.

// Dado
const map = (fn) => (lista) => lista.map(fn)
const suma = (a) => (b) => a + b

// Entonces

// No es points-free - `números` es un argumento explicito
const incrementarTodos = (números) => map(suma(1))(números)

// Es Points-free - la lista de números es un argumento implícito
const incrementarTodos2 = map(suma(1))

incrementarTodos identifica y usa el parámetro números, por lo tanto no es point-free. incrementarTodos2 esta escrita mediante la simple combinación de funciones y valores, sin hacer mención de sus argumentos. Por lo tanto es points-free.

Las definiciones de funciones escritas utilizando el estilo Point-free se ven justo como las asignaciones normales sin la utilización de function o =>.

Predicado

Un predicado es una función que retorna verdadero o falso para un valor dado. Un uso común de un predicado es en el callback del método filter de un arreglo.

const predicado = (a) => a > 2

;[1, 2, 3, 4].filter(predicado) // [3, 4]

Contratos

Pendiente

Guarded Functions

Pendiente

Categorías

Objetos con funciones asociadas que siguen ciertas reglas.

Valor

Todo lo que puede ser asignado a una función.

5
Object.freeze({nombre: 'John', edad: 30}) // La función `freeze` hace que el objeto sea inmutable.
;(a) => a
;[1]
undefined

Constante

Una variable que no puede ser reasignada una vez definida.

const cinco = 5
const juan = {nombre: 'Juan', edad: 30}

Las constantes poseen transparencia referencial. Esto quiere decir que pueden ser reemplazadas por el valor que representan sin afectar el resultado.

Con las dos constantes de arriba la siguiente expresión siempre retornara true.

juan.edad + cinco === ({name: 'Juan', edad: 30}).edad + (5)

Funtor

Un objeto que implementa una función map la cual, mientras se ejecuta sobre cada valor del objeto para producir un nuevo objeto, se adhiere a dos reglas:

// preserva la identidad
object.map(x => x) === object

y

// composición
object.map(x => f(g(x))) === object.map(g).map(f)

(Sea f, g funciones arbritarias)

Un funtor muy común en JavaScript es Array ya que cumple las dos reglas de funtor:

[1, 2, 3].map(x => x) // = [1, 2, 3]

y

const f = x => x + 1
const g = x => x * 2

;[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7]
;[1, 2, 3].map(g).map(f)     // = [3, 5, 7]

Transparencia referencial

Una expresión que puede ser reemplazada por su valor sin alterar el comportamiento del programa se dice que es referencialmente transparente.

Digamos que tenemos la función saludar:

const saludar = () => 'Hola Mundo!'

Cualquier invocación de saludar() puede ser reemplazada por Hola Mundo! , por lo tanto saludar es referencialmente transparente.

Razonamiento Ecuacional

Cuando una aplicación esta compuesta de expresiones y es libre de efectos secundarios, verdades acerca del sistema pueden ser derivadas a partir de sus partes.

Lambda

Una función anónima que puede tratarse como un valor.

;(function (a) {
  return a + 1
})

;(a) => a + 1

Lambdas a menudo se pasan como argumentos de funciones de Orden Superior.

[1, 2].map((a) => a + 1) // [2, 3]

Se puede asignar un lambda a una variables.

const add1 = (a) => a + 1

Cálculo lambda

Una rama de las matemáticas que utiliza funciones para crear un modelo universal de computación.

Monoide

Un objeto con una función que "combina" ese objeto con otro del mismo tipo.

Un monoide simple es la suma de números:

1 + 1 // 2

En este caso el número es el objeto y + es la función.

Un valor "identidad" también debe existir, el cual al combinarse con un valor no lo cambia.

El valor identidad de la suma es 0.

1 + 0 // 1

También se requiere que el agrupamiento de operaciones no afecte el resultado (asociatividad):

1 + (2 + 3) === (1 + 2) + 3 // verdadero

La concatenación de arreglos también forma un monoide.

;[1, 2].concat([3, 4]) // [1, 2, 3, 4]

El valor identidad es un arreglo vacío []

;[1, 2].concat([]) // [1, 2]

Si las funciones de identidad y composición son provistas, las funciones en si forman un monoide:

const identidad = (a) => a
const componer = (f, g) => (x) => f(g(x))

foo es cualquier función que acepte un argumento.

componer(foo, identidad) ≍ componer(identidad, foo) ≍ foo

Mónada

Una mónada es un objeto con funciones of y chain. chain es como map excepto que desanida el objeto anidado resultante.

// Implementación
Array.prototype.chain = function (f) {
  return this.reduce((acc, it) => acc.concat(f(it)), [])
}

// Uso
;['cat,dog', 'fish,bird'].chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']

// Contraste de map
;['cat,dog', 'fish,bird'].map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]

of también se conoce como return en otros lenguajes funcional. chain también se conoce como flatmap y bind en otros lenguajes funcional.

Co-mónada

Un objeto que tiene funciones extract y extend.

const CoIdentity = (v) => ({
  val: v,
  extract () {
    return this.val
  },
  extend (f) {
    return CoIdentity(f(this))
  }
})

extract toma un valor de un funtor.

CoIdentity(1).extract() // 1

extend ejecuta una función en la co-mónada. La función debe devolver el mismo tipo que la co-mónada.

CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)

Funtor aplicativo

Un funtor aplicativo es un objeto con una función ap. ap aplica una función en el objeto a un valor en otro objeto del mismo tipo.

// Implementación
Array.prototype.ap = function (xs) {
  return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}

// Ejemplo de uso
;[(a) => a + 1].ap([1]) // [2]

Esto es útil si se tiene dos objetos y se quiere aplicar una función binaria a sus contenidos.

// Arreglos que se quieren combinar
const arg1 = [1, 3]
const arg2 = [4, 5]

// función de combinación - debe ser currificada para que funcione
const suma = (x) => (y) => x + y

const sumasParcialmenteAplicadas = [suma].ap(arg1) // [(y) => 1 + y, (y) => 3 + y]

Esto nos da un arreglo de funciones sobre el cual se puede llamar ap para obtener el resultado:

sumasParcialmenteAplicadas.ap(arg2) // [5, 6, 7, 8]

Semigrupo

Un objeto que posee una función concat que lo combina con otro objeto del mismo tipo.

;[1].concat([2]) // [1, 2]

Tipo de unión

Un tipo de unión es la combinación de dos tipos juntos en otro.

JS no tiene tipos estáticos, pero digamos que inventamos un tipo NumOrString que es una suma de String y Number.

El operador + en JS funciona en cadenas y números de modo que podemos utilizar este nuevo tipo para describir sus entradas y salidas:

// add :: (NumOrString, NumOrString) -> NumOrString
const add = (a, b) => a + b

add(1, 2) // Devuelve numero 3
add('Foo', 2) // Devuelve cadena "Foo2"
add('Foo', 'Bar') // Devuelve cadena "FooBar"

Tipos de unión también se conoce como tipos algebraicos, uniones etiquetadas, o tipos de suma.

Hay un par de bibliotecas en JS que ayuda con definir y uso de tipos de unión.

Opción

Opción es un tipo de unión con dos casos llamados Alguno y Ninguno.

Opción es útil para funciones compuestas que pueden no devolver un valor.

// Definición nativa

const Alguno = (v) => ({
  val: v,
  map (f) {
    return Alguno(f(this.val))
  },
  chain (f) {
    return f(this.val)
  }
})

const Ninguno = () => ({
  map (f) {
    return this
  },
  chain (f) {
    return this
  }
})

// maybeProp :: (String, {a}) -> Opción a
const maybeProp = (key, obj) => typeof obj[key] === 'undefined' ? Ninguno() : Alguno(obj[key])

Use chain para secuenciar las funciones que devuelven Opciones

// getItem :: Cart -> Opción CartItem
const getItem = (cart) => maybeProp('item', cart)

// getPrice :: Item -> Opción Number
const getPrice = (item) => maybeProp('price', item)

// getNestedPrice :: cart -> Opción a
const getNestedPrice = (cart) => getItem(obj).chain(getPrice)

getNestedPrice({}) // Ninguno()
getNestedPrice({item: {foo: 1}}) // Ninguno()
getNestedPrice({item: {price: 9.99}}) // Alguno(9.99)

Opción es también conocido como Talvez. Alguno es también llamado Sólo. Ninguno a veces es llamado 'Nada'.

Librerías en JavaScript de programación funcional


Nota: Este repositorio ha sido un éxito gracias a las contribuciones de los colaboradores!

About

Jerga de la programación funcional en términos fáciles de entender!

License:MIT License