Acerca de la composición de funciones en JavaScript

Fantasemos sobre el tema de la composición funcional, así como aclaremos el significado del operador de composición / canalización.


TL; DR
Componer funciones como un jefe:
imagen
Implementaciones populares de compose : cuando se las llama, crean funciones nuevas y nuevas basadas en la recursividad, cuáles son las desventajas y cómo solucionar esto.


Puede considerar la función componer como una función pura que depende solo de los argumentos. Por lo tanto, al componer las mismas funciones en el mismo orden, deberíamos obtener una función idéntica, pero en el mundo de JavaScript esto no es así. Cualquier llamada a componer: devuelve una nueva función, esto lleva a la creación de más y más funciones nuevas en la memoria, así como a los problemas de su memorización, comparación y depuración.
Algo debe hacerse.


Motivación


  • Obtener identidad asociativa:

Es muy recomendable no crear nuevos objetos y reutilizar los resultados anteriores de la función componer. Uno de los problemas de desarrollador de React es la implementación de shallowCompare, que funciona con el resultado de la composición de funciones. Por ejemplo, la composición de enviar un evento con una devolución de llamada siempre creará una nueva función, lo que conducirá a una actualización del valor de la propiedad.


Las implementaciones populares de una composición no tienen identidad de valor de retorno.
Parcialmente, el problema de la identidad de la canción puede resolverse memorizando los argumentos. Sin embargo, la cuestión de la identidad asociativa sigue siendo:


 import {memoize} from 'ramda' const memoCompose = memoize(compose) memoCompose(a, b) === memoCompose(a, b) // ,   memoCompose(memoCompose(a, b), c) === memoCompose(a, memoCompose(b, c)) // ,        

  • Simplifique la depuración de la composición:

Por supuesto, el uso de funciones de tap ayuda a depurar funciones que tienen una sola expresión en el cuerpo. Sin embargo, es deseable tener una pila de llamadas lo más plana posible para la depuración.


  • Deshágase de los gastos generales relacionados con la recursividad:

La implementación recursiva de la composición funcional tiene una sobrecarga, creando nuevos elementos en la pila de llamadas. Cuando llama a una composición de 5 o más funciones, esto se nota claramente. Y utilizando enfoques funcionales en el desarrollo, es necesario construir composiciones a partir de docenas de funciones muy simples.


Solución


Haga un monoide (o un semigroupoid con soporte para la especificación de categoría) en términos de tierra de fantasía:


 import compose, {identity} from 'lazy-compose' import {add} from 'ramda' const a = add(1) const b = add(2) const c = add(3) test('Laws', () => { compose(a, compose(b, c)) === compose(compose(a, b), c) //  compose(a, identity) === a //right identity compose(identity, a) === a //left identity } 

Casos de uso


  • Útil para memorizar composiciones compuestas cuando se trabaja con editores. Por ejemplo para redux / mapStateToProps y
    volver a seleccionar
  • La composición de las lentes.

Puede crear y reutilizar lentes estrictamente equivalentes enfocadas en el mismo lugar.


  import {lensProp, memoize} from 'ramda' import compose from 'lazy-compose' const constantLens = memoize(lensProp) const lensA = constantLens('a') const lensB = constantLens('b') const lensC = constantLens('c') const lensAB = compose(lensB, lensA) console.log( compose(lensC, lensAB) === compose(lensC, lensB, lensA) ) 

  • Callbacks memorizados, con la capacidad de componer hasta la función final de enviar un evento.

En este ejemplo, se enviará la misma devolución de llamada a los elementos de la lista.


 ```jsx import {compose, constant} from './src/lazyCompose' // constant - returns the same memoized function for each argrum // just like React.useCallback import {compose, constant} from 'lazy-compose' const List = ({dispatch, data}) => data.map( id => <Button key={id} onClick={compose(dispatch, makeAction, contsant(id))} /> ) const Button = React.memo( props => <button {...props} /> ) const makeAction = payload => ({ type: 'onClick', payload, }) ``` 

  • Composición perezosa de componentes React sin crear componentes de orden superior. En este caso, la composición perezosa colapsará el conjunto de funciones, sin crear cierres adicionales. Esta pregunta preocupa a muchos desarrolladores que usan la biblioteca recomponer.


     import {memoize, mergeRight} from 'ramda' import {constant, compose} from './src/lazyCompose' const defaultProps = memoize(mergeRight) const withState = memoize( defaultState => props => { const [state, setState] = React.useState(defaultState) return {...props, state, setState} } ) const Component = ({value, label, ...props)) => <label {...props}>{label} : {value}</label> const withCounter = compose( ({setState, state, ...props}) => ({ ...props value: state, onClick: compose(setState, constant(state + 1)) }), withState(0), ) const Counter = compose( Component, withCounter, defaultProps({label: 'Clicks'}), ) 

  • Mónadas y solicitantes (en términos de tierra de fantasía) con equivalencia estricta a través del almacenamiento en caché del resultado de la composición. Si accede al diccionario de objetos creados previamente dentro del constructor de tipos, obtendrá lo siguiente:



  type Info = { age?: number } type User = { info?: Info } const mayBeAge = LazyMaybe<Info>.of(identity) .map(getAge) .contramap(getInfo) const age = mayBeAge.ap(data) const maybeAge2 = LazyMaybe<User>.of(compose(getAge, getInfo)) console.log(maybeAge === maybeAge2) //   ,      //           

He estado usando este enfoque durante mucho tiempo, diseñé el repositorio aquí .
Paquete NPM: npm i lazy-compose .


Es interesante obtener comentarios sobre el cierre de la memoria caché de funciones creadas en tiempo de ejecución dependiendo de los cierres.


UPD
Preveo preguntas obvias:
Sí, puede reemplazar Map con WeakMap.
Sí, debe permitir conectar un caché de terceros como middleware.
No debe organizar un debate sobre el tema de los cachés; no existe una estrategia de almacenamiento en caché ideal.
Por qué cola y cabeza, si todo está en la lista: cola y cabeza, parte de la implementación con la memorización basada en partes de la composición, y no cada una funciona por separado.

Source: https://habr.com/ru/post/es432196/


All Articles