关于JavaScript中的函数组成

让我们幻想一下功能组合的主题,并阐明组合/管道运算符的含义。


TL; DR
像老板一样编写函数:
图片
compose流行实现-调用时,它们基于递归创建新的函数,缺点是什么,如何解决这个问题。


您可以将compose函数视为仅依赖于参数的纯函数。 因此,以相同的顺序组成相同的函数,我们应该获得相同的函数,但是在JavaScript世界中却并非如此。 任何对compose的调用-都会返回一个新函数,这会导致在内存中创建越来越多的新函数,以及它们的记忆,比较和调试问题。
必须做些事情。


动机


  • 获取关联身份:

强烈建议不要创建新对象并重用compose函数的先前结果。 React开发人员的问题之一是shallowCompare的实现,该实现可与功能组合的结果一起使用。 例如,使用回调发送事件的组合将始终创建一个新函数,这将导致属性值的更新。


组合的流行实现没有返回值标识。
可以通过记忆参数来部分解决歌曲身份问题。 但是,关联身份的问题仍然存在:


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

  • 简化成分调试:

当然,使用tap函数有助于调试体内具有单个表达式的函数。 但是,希望具有尽可能平坦的调用堆栈以进行调试。


  • 摆脱与递归相关的开销:

功能组合的递归实现会产生开销,从而在调用堆栈中创建新元素。 当您调用5个或更多函数的组合时,这显然很明显。 在开发中使用功能性方法时,有必要从数十种非常简单的功能构建组合。


解决方案


根据幻想领域制作一个monoid(或支持类别说明的半群):


 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 } 

用例


  • 与编辑一起使用时,可用于记忆复合作品。 例如对于redux / mapStateToProps和
    重新选择。
  • 镜片的组成。

您可以创建并重用聚焦在同一位置的等效镜头。


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

  • 记忆化的回调,能够构成发送事件的最终功能。

在此示例中,相同的回调将发送到列表项。


 ```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, }) ``` 

  • React组件的惰性组成,而不创建更高阶的组件。 在这种情况下,惰性组合将折叠函数数组,而不会创建其他闭包。 这个问题使许多使用重组库的开发人员感到担忧。


     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'}), ) 

  • 通过缓存合成结果,严格限制了Monads和Applicatives(就幻想世界而言)。 如果访问类型构造函数中以前创建的对象的字典,则会得到以下内容:



  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) //   ,      //           

我使用这种方法已有很长时间了, 我在这里设计了存储库
NPM软件包: npm i lazy-compose


获得有关关闭运行时创建的函数的缓存的反馈(取决于关闭)很有趣。


UPD
我预见到明显的问题:
是的,您可以使用WeakMap替换Map。
是的,您需要使第三方缓存作为中间件连接成为可能。
您不应就缓存问题进行辩论;没有理想的缓存策略。
为什么要用尾巴和头部,如果所有内容都在列表中-尾巴和头部,则该实现的一部分基于基于组成部分的备注,而不是将每个功能分开。

Source: https://habr.com/ru/post/zh-CN432196/


All Articles